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 | |
| parent | 0e005f2ca6a3d9e572ed18b51c720d8bea907118 (diff) | |
Improve type safety for playwright stuff (#727)
| -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}`); +    } +} |