aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2024-02-24 23:32:39 -0500
committerGitHub <noreply@github.com>2024-02-25 04:32:39 +0000
commita21948daf6210f67955ae4f98a81e21b8cf9f1f2 (patch)
tree9d4d39c25e23c3c25fbd0f3a557a79c05c825e09 /test
parent0e005f2ca6a3d9e572ed18b51c720d8bea907118 (diff)
Improve type safety for playwright stuff (#727)
Diffstat (limited to 'test')
-rw-r--r--test/playwright/integration.spec.js34
-rw-r--r--test/playwright/playwright-util.js128
2 files changed, 91 insertions, 71 deletions
diff --git a/test/playwright/integration.spec.js b/test/playwright/integration.spec.js
index 4957b676..8e641397 100644
--- a/test/playwright/integration.spec.js
+++ b/test/playwright/integration.spec.js
@@ -17,12 +17,12 @@
import path from 'path';
import {createDictionaryArchive} from '../../dev/util.js';
+import {deferPromise} from '../../ext/js/core/utilities.js';
import {
expect,
- expectedAddNoteBody,
+ getExpectedAddNoteBody,
+ getMockModelFields,
mockAnkiRouteHandler,
- mockModelFieldNames,
- mockModelFieldsToAnkiValues,
root,
test,
writeToClipboardFromPage
@@ -45,16 +45,21 @@ test('search clipboard', async ({page, extensionId}) => {
test('anki add', async ({context, page, extensionId}) => {
// Mock anki routes
- /** @type {?(value: unknown) => void} */
- let resolve = null;
- const addNotePromise = new Promise((res) => {
- resolve = res;
- });
+ /** @type {import('core').DeferredPromiseDetails<Record<string, unknown>>} */
+ const addNotePromiseDetails = deferPromise();
await context.route(/127.0.0.1:8765\/*/, (route) => {
void mockAnkiRouteHandler(route);
const req = route.request();
- if (req.url().includes('127.0.0.1:8765') && req.postDataJSON().action === 'addNote') {
- /** @type {(value: unknown) => void} */ (resolve)(req.postDataJSON());
+ if (req.url().includes('127.0.0.1:8765')) {
+ /** @type {unknown} */
+ const requestJson = req.postDataJSON();
+ if (
+ typeof requestJson === 'object' &&
+ requestJson !== null &&
+ /** @type {Record<string, unknown>} */ (requestJson).action === 'addNote'
+ ) {
+ addNotePromiseDetails.resolve(/** @type {Record<string, unknown>} */ (requestJson));
+ }
}
});
@@ -79,8 +84,9 @@ test('anki add', async ({context, page, extensionId}) => {
await page.locator('[data-modal-action="show,anki-cards"]').click();
await page.locator('select.anki-card-deck').selectOption('Mock Deck');
await page.locator('select.anki-card-model').selectOption('Mock Model');
- for (const modelField of mockModelFieldNames) {
- await page.locator(`[data-setting="anki.terms.fields.${modelField}"]`).fill(/** @type {string} */ (mockModelFieldsToAnkiValues[modelField]));
+ const mockFields = getMockModelFields();
+ for (const [modelField, value] of mockFields) {
+ await page.locator(`[data-setting="anki.terms.fields.${modelField}"]`).fill(value);
}
await page.locator('#anki-cards-modal > div > div.modal-footer > button:nth-child(2)').click();
await writeToClipboardFromPage(page, '読むの例文');
@@ -94,6 +100,6 @@ test('anki add', async ({context, page, extensionId}) => {
}).toPass({timeout: 5000});
await page.locator('#search-textbox').press('Enter');
await page.locator('[data-mode="term-kanji"]').click();
- const addNoteReqBody = await addNotePromise;
- expect(addNoteReqBody).toMatchObject(expectedAddNoteBody);
+ const addNoteReqBody = await addNotePromiseDetails.promise;
+ expect(addNoteReqBody).toMatchObject(getExpectedAddNoteBody());
});
diff --git a/test/playwright/playwright-util.js b/test/playwright/playwright-util.js
index 653112c6..1ea2e604 100644
--- a/test/playwright/playwright-util.js
+++ b/test/playwright/playwright-util.js
@@ -51,32 +51,39 @@ export const test = base.extend({
export const expect = test.expect;
-export const mockModelFieldNames = [
- 'Word',
- 'Reading',
- 'Audio',
- 'Sentence'
-];
-
-/** @type {{[key: string]: string|undefined}} */
-export const mockModelFieldsToAnkiValues = {
- Word: '{expression}',
- Reading: '{furigana-plain}',
- Sentence: '{clipboard-text}',
- Audio: '{audio}'
-};
+/**
+ * @returns {Map<string, string>}
+ */
+export function getMockModelFields() {
+ return new Map([
+ ['Word', '{expression}'],
+ ['Reading', '{furigana-plain}'],
+ ['Sentence', '{clipboard-text}'],
+ ['Audio', '{audio}']
+ ]);
+}
/**
* @param {import('playwright').Route} route
* @returns {Promise<void>}
*/
export async function mockAnkiRouteHandler(route) {
- const reqBody = route.request().postDataJSON();
- const respBody = ankiRouteResponses[reqBody.action];
- if (!respBody) {
- return route.abort();
+ try {
+ /** @type {unknown} */
+ const requestJson = route.request().postDataJSON();
+ if (typeof requestJson !== 'object' || requestJson === null) {
+ throw new Error(`Invalid request type: ${typeof requestJson}`);
+ }
+ const body = getResponseBody(/** @type {import('core').SerializableObject} */ (requestJson).action);
+ const responseJson = {
+ status: 200,
+ contentType: 'text/json',
+ body: JSON.stringify(body)
+ };
+ await route.fulfill(responseJson);
+ } catch {
+ return await route.abort();
}
- await route.fulfill(respBody);
}
/**
@@ -88,45 +95,52 @@ export const writeToClipboardFromPage = async (page, text) => {
await page.evaluate(`navigator.clipboard.writeText('${text}')`);
};
-export const expectedAddNoteBody = {
- version: 2,
- action: 'addNote',
- params: {
- note: {
- fields: {
- Word: '読む',
- Reading: '読[よ]む',
- Audio: '[sound:mock_audio.mp3]',
- Sentence: '読むの例文'
- },
- tags: ['yomitan'],
- deckName: 'Mock Deck',
- modelName: 'Mock Model',
- options: {
- allowDuplicate: false,
- duplicateScope: 'collection',
- duplicateScopeOptions: {
- deckName: null,
- checkChildren: false,
- checkAllModels: false
+/**
+ * @returns {Record<string, unknown>}
+ */
+export function getExpectedAddNoteBody() {
+ return {
+ version: 2,
+ action: 'addNote',
+ params: {
+ note: {
+ fields: {
+ Word: '読む',
+ Reading: '読[よ]む',
+ Audio: '[sound:mock_audio.mp3]',
+ Sentence: '読むの例文'
+ },
+ tags: ['yomitan'],
+ deckName: 'Mock Deck',
+ modelName: 'Mock Model',
+ options: {
+ allowDuplicate: false,
+ duplicateScope: 'collection',
+ duplicateScopeOptions: {
+ deckName: null,
+ checkChildren: false,
+ checkAllModels: false
+ }
}
}
}
- }
-};
-
-const baseAnkiResp = {
- status: 200,
- contentType: 'text/json'
-};
+ };
+}
-/** @type {{[key: string]: import('core').SerializableObject}} */
-const ankiRouteResponses = {
- version: Object.assign({body: JSON.stringify(6)}, baseAnkiResp),
- deckNames: Object.assign({body: JSON.stringify(['Mock Deck'])}, baseAnkiResp),
- modelNames: Object.assign({body: JSON.stringify(['Mock Model'])}, baseAnkiResp),
- modelFieldNames: Object.assign({body: JSON.stringify(mockModelFieldNames)}, baseAnkiResp),
- canAddNotes: Object.assign({body: JSON.stringify([true, true])}, baseAnkiResp),
- storeMediaFile: Object.assign({body: JSON.stringify('mock_audio.mp3')}, baseAnkiResp),
- addNote: Object.assign({body: JSON.stringify(102312488912)}, baseAnkiResp)
-};
+/**
+ * @param {unknown} action
+ * @returns {unknown}
+ * @throws {Error}
+ */
+function getResponseBody(action) {
+ switch (action) {
+ case 'version': return 6;
+ case 'deckNames': return ['Mock Deck'];
+ case 'modelNames': return ['Mock Model'];
+ case 'modelFieldNames': return [...getMockModelFields().keys()];
+ case 'canAddNotes': return [true, true];
+ case 'storeMediaFile': return 'mock_audio.mp3';
+ case 'addNote': return 102312488912;
+ default: throw new Error(`Unknown action: ${action}`);
+ }
+}