diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2024-02-24 23:32:39 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-25 04:32:39 +0000 |
commit | a21948daf6210f67955ae4f98a81e21b8cf9f1f2 (patch) | |
tree | 9d4d39c25e23c3c25fbd0f3a557a79c05c825e09 /test/playwright | |
parent | 0e005f2ca6a3d9e572ed18b51c720d8bea907118 (diff) |
Improve type safety for playwright stuff (#727)
Diffstat (limited to 'test/playwright')
-rw-r--r-- | test/playwright/integration.spec.js | 34 | ||||
-rw-r--r-- | test/playwright/playwright-util.js | 128 |
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}`); + } +} |