diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b46ba2..5d2acb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **Browser/native bundling of the morphology parsers.** `Grid3VerbsParser` + statically imported Node's `path` and `adm-zip`, and `TDSnapLexiconParser` + statically required `better-sqlite3`, so importing the browser or metrics + entry points failed in Metro (React Native/Expo) with + `Unable to resolve module path`. Both parsers now take a `FileAdapter`, + `Grid3VerbsParser` reads archives through `getZipAdapter` (Node `adm-zip` / + browser `jszip`), and `TDSnapLexiconParser` opens databases through + `openSqliteDatabase` (`better-sqlite3` on Node, `sql.js` in the browser), so + these modules are safe to bundle for web and native. + + **Breaking change:** `Grid3VerbsParser.parseZip`/`parseZipDetailed`/ + `parseXmlFileDetailed`/`parseCustomDirectory`, `TDSnapLexiconParser.parseDb`, + `WordFormGenerator.generateFromPos` and `MetricsCalculator.analyze` are now + asynchronous. + - **OBF/OBZ manifest `root` bug.** `ObfProcessor.saveFromTree` and `saveModifiedTree` set `manifest.root` to the board **ID** (e.g. `"1"`) instead of the board **file path** (e.g. `"1.obf"`). This produced OBZ diff --git a/src/cli/index.ts b/src/cli/index.ts index 14eba9e..dc465b2 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -578,7 +578,7 @@ program } const calculator = new MetricsCalculator(); - const metrics = calculator.analyze(tree, { + const metrics = await calculator.analyze(tree, { scanningConfig, useSmartGrammar: options.smartGrammar, }); @@ -669,7 +669,7 @@ program const tree = await processor.loadIntoTree(file); // analyze() first: it expands morphological predictions on the tree, // which dumpVocabulary() needs for the word-form counts. - const metrics = new MetricsCalculator().analyze(tree, { + const metrics = await new MetricsCalculator().analyze(tree, { useSmartGrammar: options.smartGrammar, }); dump = dumpVocabulary(tree, { metrics }); diff --git a/src/utilities/analytics/metrics/core.ts b/src/utilities/analytics/metrics/core.ts index e38afec..3d3c0d0 100644 --- a/src/utilities/analytics/metrics/core.ts +++ b/src/utilities/analytics/metrics/core.ts @@ -47,7 +47,7 @@ export class MetricsCalculator { * @param options - Optional configuration for metrics calculation * @returns Complete metrics result */ - analyze(tree: AACTree, options: MetricsOptions = {}): MetricsResult { + async analyze(tree: AACTree, options: MetricsOptions = {}): Promise { // Get root board - prioritize tree.rootId, then fall back to boards with no parentId let rootBoard: AACPage | undefined; if (tree.rootId) { @@ -122,7 +122,7 @@ export class MetricsCalculator { // Expand morphological predictions from POS tags if enabled or auto-detected const useSmartGrammar = options.useSmartGrammar === true || this.treeHasPosTags(tree); if (useSmartGrammar) { - this.expandMorphologicalPredictions(tree, options); + await this.expandMorphologicalPredictions(tree, options); } if (useSmartGrammar) { @@ -789,7 +789,10 @@ export class MetricsCalculator { * button's predictions array. This is done as a pre-processing step * before calculateWordFormMetrics assigns effort to each form. */ - private expandMorphologicalPredictions(tree: AACTree, options: MetricsOptions): void { + private async expandMorphologicalPredictions( + tree: AACTree, + options: MetricsOptions + ): Promise { const locale = options.morphologyLocale || 'en-gb'; let morph: MorphologyEngine; @@ -797,7 +800,7 @@ export class MetricsCalculator { // eslint-disable-next-line @typescript-eslint/no-var-requires const { TDSnapLexiconParser } = require('../morphology/tdsnapLexiconParser'); const parser = new TDSnapLexiconParser(); - const lexiconData = parser.parseDb(options.tdsnapLexiconPath, locale.replace('-', '_')); + const lexiconData = await parser.parseDb(options.tdsnapLexiconPath, locale.replace('-', '_')); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument morph = MorphologyEngine.fromTDSnapLexicon(lexiconData); this.expandTDSnapPredictions(tree, morph); @@ -808,7 +811,7 @@ export class MetricsCalculator { // eslint-disable-next-line @typescript-eslint/no-var-requires const { Grid3VerbsParser } = require('../morphology/grid3VerbsParser'); const parser = new Grid3VerbsParser(); - const verbForms = parser.parseZip(options.grid3VerbsPath); + const verbForms = await parser.parseZip(options.grid3VerbsPath); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument morph = MorphologyEngine.fromGrid3Verbs(verbForms); this.expandGrid3Predictions(tree, morph); diff --git a/src/utilities/analytics/morphology/grid3VerbsParser.ts b/src/utilities/analytics/morphology/grid3VerbsParser.ts index abb0d1b..8aab8fe 100644 --- a/src/utilities/analytics/morphology/grid3VerbsParser.ts +++ b/src/utilities/analytics/morphology/grid3VerbsParser.ts @@ -1,6 +1,7 @@ import { XMLParser } from 'fast-xml-parser'; -import AdmZip from 'adm-zip'; -import { join, dirname, basename } from 'path'; +import { defaultFileAdapter } from '../../../utils/io'; +import type { FileAdapter } from '../../../utils/io'; +import { getZipAdapter } from '../../../utils/zip'; import type { VerbFormWithConditions, Grid3VerbFormsDetailed } from './types'; export interface Grid3VerbForms { @@ -32,6 +33,11 @@ export class Grid3VerbsParser { ignoreDeclaration: true, textNodeName: '#text', }); + private fileAdapter: FileAdapter; + + constructor(fileAdapter: FileAdapter = defaultFileAdapter) { + this.fileAdapter = fileAdapter; + } parseXml(xmlContent: string, locale?: string): Grid3VerbForms { const data = this.parser.parse(xmlContent); @@ -77,74 +83,73 @@ export class Grid3VerbsParser { return { locale: detectedLocale, verbs: result }; } - /* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-argument */ - parseXmlFileDetailed(filePath: string): Grid3VerbFormsDetailed { - const fs = require('fs'); - const xml = fs.readFileSync(filePath, 'utf-8'); + async parseXmlFileDetailed(filePath: string): Promise { + const xml = await this.fileAdapter.readTextFromInput(filePath); return this.parseXmlDetailed(xml); } - /* eslint-enable @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-argument */ - - parseZip(zipPath: string): Grid3VerbForms { - const zip = new AdmZip(zipPath); - const entries = zip.getEntries(); - const verbEntry = entries.find((e) => e.entryName.toLowerCase().endsWith('verbs.xml')); - if (!verbEntry) { - const locale = basename(dirname(zipPath)); + + private async readVerbsXml(zipPath: string): Promise { + const zip = await getZipAdapter(zipPath, this.fileAdapter); + const verbEntry = zip.listFiles().find((entry) => entry.toLowerCase().endsWith('verbs.xml')); + if (!verbEntry) return null; + return this.fileAdapter.readTextFromInput(await zip.readFile(verbEntry)); + } + + async parseZip(zipPath: string): Promise { + const xml = await this.readVerbsXml(zipPath); + if (xml === null) { + const locale = this.fileAdapter.basename(this.fileAdapter.dirname(zipPath)); return { locale, verbs: new Map() }; } - const xml = verbEntry.getData().toString('utf-8'); return this.parseXml(xml); } - parseZipDetailed(zipPath: string): Grid3VerbFormsDetailed { - const zip = new AdmZip(zipPath); - const entries = zip.getEntries(); - const verbEntry = entries.find((e) => e.entryName.toLowerCase().endsWith('verbs.xml')); - if (!verbEntry) { - const locale = basename(dirname(zipPath)); + async parseZipDetailed(zipPath: string): Promise { + const xml = await this.readVerbsXml(zipPath); + if (xml === null) { + const locale = this.fileAdapter.basename(this.fileAdapter.dirname(zipPath)); return { locale, verbs: new Map() }; } - const xml = verbEntry.getData().toString('utf-8'); return this.parseXmlDetailed(xml); } - // eslint-disable-next-line @typescript-eslint/require-await async parseLocale(locale: string, grid3InstallPath?: string): Promise { - const installPath = grid3InstallPath || this.getDefaultInstallPath(); + const installPath = grid3InstallPath || (await this.getDefaultInstallPath()); if (!installPath) { return { locale, verbs: new Map() }; } - const zipPath = join(installPath, 'Locale', locale, 'verbs', 'verbs.zip'); + const zipPath = this.fileAdapter.join(installPath, 'Locale', locale, 'verbs', 'verbs.zip'); try { - return this.parseZip(zipPath); + return await this.parseZip(zipPath); } catch { return { locale, verbs: new Map() }; } } async parseInstalledLocales(grid3InstallPath?: string): Promise> { - const installPath = grid3InstallPath || this.getDefaultInstallPath(); + const installPath = grid3InstallPath || (await this.getDefaultInstallPath()); const results = new Map(); if (!installPath) return results; - const fs = await import('fs'); - const localeDir = join(installPath, 'Locale'); + const localeDir = this.fileAdapter.join(installPath, 'Locale'); let locales: string[]; try { - locales = fs - .readdirSync(localeDir) - .filter((d) => fs.statSync(join(localeDir, d)).isDirectory()); + const entries = await this.fileAdapter.listDir(localeDir); + locales = []; + for (const entry of entries) { + if (await this.fileAdapter.isDirectory(this.fileAdapter.join(localeDir, entry))) { + locales.push(entry); + } + } } catch { return results; } for (const locale of locales) { - const verbsZip = join(localeDir, locale, 'verbs', 'verbs.zip'); + const verbsZip = this.fileAdapter.join(localeDir, locale, 'verbs', 'verbs.zip'); try { - fs.accessSync(verbsZip, fs.constants.R_OK); - const forms = this.parseZip(verbsZip); - results.set(locale, forms); + if (!(await this.fileAdapter.pathExists(verbsZip))) continue; + results.set(locale, await this.parseZip(verbsZip)); } catch { // No verbs.zip for this locale } @@ -164,23 +169,24 @@ export class Grid3VerbsParser { * This allows users to supply morphology data copied from any Grid 3 * installation without needing Grid 3 installed on this machine. */ - parseCustomDirectory(dirPath: string, detailed?: false): Map; + parseCustomDirectory(dirPath: string, detailed?: false): Promise>; parseCustomDirectory( dirPath: string, detailed: true - ): Map; - // eslint-disable-next-line @typescript-eslint/no-var-requires - parseCustomDirectory( + ): Promise>; + async parseCustomDirectory( dirPath: string, detailed = false - ): Map | Map { - /* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ - const fs = require('fs'); + ): Promise | Map> { let locales: string[]; try { - locales = fs - .readdirSync(dirPath) - .filter((d: string) => fs.statSync(join(dirPath, d)).isDirectory()); + const entries = await this.fileAdapter.listDir(dirPath); + locales = []; + for (const entry of entries) { + if (await this.fileAdapter.isDirectory(this.fileAdapter.join(dirPath, entry))) { + locales.push(entry); + } + } } catch { return new Map(); } @@ -188,10 +194,10 @@ export class Grid3VerbsParser { if (detailed) { const results = new Map(); for (const locale of locales) { - const verbsZip = join(dirPath, locale, 'verbs', 'verbs.zip'); + const verbsZip = this.fileAdapter.join(dirPath, locale, 'verbs', 'verbs.zip'); try { - fs.accessSync(verbsZip, fs.constants.R_OK); - results.set(locale, this.parseZipDetailed(verbsZip)); + if (!(await this.fileAdapter.pathExists(verbsZip))) continue; + results.set(locale, await this.parseZipDetailed(verbsZip)); } catch { // No verbs.zip for this locale } @@ -201,19 +207,18 @@ export class Grid3VerbsParser { const results = new Map(); for (const locale of locales) { - const verbsZip = join(dirPath, locale, 'verbs', 'verbs.zip'); + const verbsZip = this.fileAdapter.join(dirPath, locale, 'verbs', 'verbs.zip'); try { - fs.accessSync(verbsZip, fs.constants.R_OK); - results.set(locale, this.parseZip(verbsZip)); + if (!(await this.fileAdapter.pathExists(verbsZip))) continue; + results.set(locale, await this.parseZip(verbsZip)); } catch { // No verbs.zip for this locale } } return results; - /* eslint-enable @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ } - private getDefaultInstallPath(): string | null { + private async getDefaultInstallPath(): Promise { if (typeof process === 'undefined' || process.platform !== 'win32') { return null; } @@ -222,10 +227,8 @@ export class Grid3VerbsParser { 'C:\\Program Files\\Smartbox\\Grid 3', ]; try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const fs = require('fs'); for (const p of paths) { - if (fs.existsSync(p)) return p; + if (await this.fileAdapter.pathExists(p)) return p; } } catch { // Grid 3 not installed diff --git a/src/utilities/analytics/morphology/tdsnapLexiconParser.ts b/src/utilities/analytics/morphology/tdsnapLexiconParser.ts index ecc6d7e..3c42ac0 100644 --- a/src/utilities/analytics/morphology/tdsnapLexiconParser.ts +++ b/src/utilities/analytics/morphology/tdsnapLexiconParser.ts @@ -1,3 +1,8 @@ +import { defaultFileAdapter } from '../../../utils/io'; +import type { FileAdapter } from '../../../utils/io'; +import { openSqliteDatabase } from '../../../utils/sqlite'; +import type { SqliteDatabaseAdapter } from '../../../utils/sqlite'; + export interface TDSnapLexiconForm { tag: string; form: string; @@ -20,17 +25,27 @@ interface PosSubclassRow { } export class TDSnapLexiconParser { - parseDb(dbPath: string, locale?: string): TDSnapLexiconData { + private fileAdapter: FileAdapter; + + constructor(fileAdapter: FileAdapter = defaultFileAdapter) { + this.fileAdapter = fileAdapter; + } + + async parseDb(dbPath: string, locale?: string): Promise { const detectedLocale = locale || this.inferLocale(dbPath); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const Database = require('better-sqlite3'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const db = new Database(dbPath, { readonly: true }) as import('better-sqlite3').Database; + const { db, cleanup } = await openSqliteDatabase(dbPath, { + readonly: true, + fileAdapter: this.fileAdapter, + }); try { return this.extractAll(db, detectedLocale); } finally { - db.close(); + if (cleanup) { + await cleanup(); + } else { + db.close(); + } } } @@ -39,7 +54,7 @@ export class TDSnapLexiconParser { return match ? match[1] : 'unknown'; } - private extractAll(db: import('better-sqlite3').Database, locale: string): TDSnapLexiconData { + private extractAll(db: SqliteDatabaseAdapter, locale: string): TDSnapLexiconData { const words = new Map(); const subclassCache = new Map(); diff --git a/src/utilities/analytics/morphology/wordFormGenerator.ts b/src/utilities/analytics/morphology/wordFormGenerator.ts index cfe4d6d..700aa4d 100644 --- a/src/utilities/analytics/morphology/wordFormGenerator.ts +++ b/src/utilities/analytics/morphology/wordFormGenerator.ts @@ -62,16 +62,16 @@ export class WordFormGenerator { return this.deduplicate(result); } - generateFromPos( + async generateFromPos( base: string, pos: string, engine: MorphologyEngine, grid3Parser: Grid3VerbsParser, verbsZipPath?: string, lang: string = 'en' - ): AstericsWordForm[] { + ): Promise { if (verbsZipPath) { - const detailed = grid3Parser.parseZipDetailed(verbsZipPath); + const detailed = await grid3Parser.parseZipDetailed(verbsZipPath); const forms = detailed.verbs.get(base) || detailed.verbs.get(base.toLowerCase()); if (forms && forms.length > 0) { return this.generateFromGrid3Conditions(base, forms, lang); diff --git a/test/grammarGating.test.ts b/test/grammarGating.test.ts index 49ab1e1..e033e9a 100644 --- a/test/grammarGating.test.ts +++ b/test/grammarGating.test.ts @@ -153,7 +153,7 @@ describe('Grammar Gating', () => { calc = new MetricsCalculator(); }); - test('pages WITHOUT suffix buttons get NO morphology forms', () => { + test('pages WITHOUT suffix buttons get NO morphology forms', async () => { const tree = makeTree([ { id: 'home', @@ -162,13 +162,13 @@ describe('Grammar Gating', () => { }, ]); - calc.analyze(tree, { useSmartGrammar: true }); + await calc.analyze(tree, { useSmartGrammar: true }); const goBtn = tree.pages['home'].grid[0][0]; expect(goBtn?.predictions).toBeUndefined(); }); - test('pages WITH suffix buttons get morphology forms', () => { + test('pages WITH suffix buttons get morphology forms', async () => { const tree = makeTree([ { id: 'home', @@ -182,14 +182,14 @@ describe('Grammar Gating', () => { }, ]); - calc.analyze(tree, { useSmartGrammar: true }); + await calc.analyze(tree, { useSmartGrammar: true }); const goBtn = tree.pages['home'].grid[0][0]; expect(goBtn?.predictions).toBeDefined(); expect((goBtn?.predictions || []).length).toBeGreaterThan(0); }); - test('pages with only -s suffix do not produce adjective comparative/superlative forms', () => { + test('pages with only -s suffix do not produce adjective comparative/superlative forms', async () => { const tree = makeTree([ { id: 'home', @@ -203,7 +203,7 @@ describe('Grammar Gating', () => { }, ]); - calc.analyze(tree, { useSmartGrammar: true }); + await calc.analyze(tree, { useSmartGrammar: true }); const bigBtn = tree.pages['home'].grid[0][0]; if (bigBtn?.predictions && bigBtn.predictions.length > 0) { @@ -212,7 +212,7 @@ describe('Grammar Gating', () => { } }); - test('Magic Wand pages (with all 6 suffixes) produce full forms', () => { + test('Magic Wand pages (with all 6 suffixes) produce full forms', async () => { const tree = makeTree([ { id: 'home', @@ -231,7 +231,7 @@ describe('Grammar Gating', () => { }, ]); - calc.analyze(tree, { useSmartGrammar: true }); + await calc.analyze(tree, { useSmartGrammar: true }); const bigBtn = tree.pages['home'].grid[0][0]; expect(bigBtn?.predictions).toBeDefined(); @@ -239,7 +239,7 @@ describe('Grammar Gating', () => { expect(bigBtn?.predictions).toContain('biggest'); }); - test('noun on page with -s suffix gets plural form', () => { + test('noun on page with -s suffix gets plural form', async () => { const tree = makeTree([ { id: 'home', @@ -253,7 +253,7 @@ describe('Grammar Gating', () => { }, ]); - calc.analyze(tree, { useSmartGrammar: true }); + await calc.analyze(tree, { useSmartGrammar: true }); const bookBtn = tree.pages['home'].grid[0][0]; expect(bookBtn?.predictions).toBeDefined(); @@ -268,7 +268,7 @@ describe('Grammar Gating', () => { calc = new MetricsCalculator(); }); - test('when no Inflector buttons exist, no morphology forms are generated', () => { + test('when no Inflector buttons exist, no morphology forms are generated', async () => { const tree = makeTree([ { id: 'home', @@ -277,7 +277,7 @@ describe('Grammar Gating', () => { }, ]); - calc.analyze(tree, { useSmartGrammar: true, tdsnapLexiconPath: undefined }); + await calc.analyze(tree, { useSmartGrammar: true, tdsnapLexiconPath: undefined }); const catBtn = tree.pages['home'].grid[0][0]; expect(catBtn?.predictions).toBeUndefined(); @@ -390,7 +390,7 @@ describe('Grammar Gating', () => { calc = new MetricsCalculator(); }); - test('returns no smart grammar when only Suffix buttons exist', () => { + test('returns no smart grammar when only Suffix buttons exist', async () => { const _tree = makeTree([ { id: 'home', @@ -404,11 +404,11 @@ describe('Grammar Gating', () => { }, ]); - const result = calc.analyze(_tree); + const result = await calc.analyze(_tree); expect(result.total_words).toBe(0); }); - test('returns no smart grammar when only Inflector buttons exist', () => { + test('returns no smart grammar when only Inflector buttons exist', async () => { const _tree = makeTree([ { id: 'home', @@ -426,11 +426,11 @@ describe('Grammar Gating', () => { }, ]); - const result = calc.analyze(_tree); + const result = await calc.analyze(_tree); expect(result.total_words).toBe(0); }); - test('real POS tags trigger smart grammar even alongside Suffix buttons', () => { + test('real POS tags trigger smart grammar even alongside Suffix buttons', async () => { const tree = makeTree([ { id: 'home', @@ -444,7 +444,7 @@ describe('Grammar Gating', () => { }, ]); - const result = calc.analyze(tree); + const result = await calc.analyze(tree); expect(result.total_words).toBeGreaterThan(0); }); }); diff --git a/test/grid3VerbsParser.test.ts b/test/grid3VerbsParser.test.ts index b4d9d72..22a6836 100644 --- a/test/grid3VerbsParser.test.ts +++ b/test/grid3VerbsParser.test.ts @@ -92,8 +92,8 @@ describe('Grid3VerbsParser - Synthetic XML (always runs)', () => { describe('parseXmlDetailed (forms with conditions)', () => { let detailed: Map }>>; - beforeAll(() => { - const result = parser.parseXmlFileDetailed(SYNTHETIC_XML); + beforeAll(async () => { + const result = await parser.parseXmlFileDetailed(SYNTHETIC_XML); detailed = result.verbs; }); @@ -140,7 +140,7 @@ describe('Grid3VerbsParser - Synthetic XML (always runs)', () => { }); describe('parseZip with synthetic data', () => { - test('can parse a zip file containing verbs.xml', () => { + test('can parse a zip file containing verbs.xml', async () => { // Create a temporary zip with our synthetic XML // eslint-disable-next-line @typescript-eslint/no-var-requires const AdmZip = require('adm-zip'); @@ -153,7 +153,7 @@ describe('Grid3VerbsParser - Synthetic XML (always runs)', () => { zip.addFile('verbs.xml', fs.readFileSync(SYNTHETIC_XML)); zip.writeZip(zipPath); - const result = parser.parseZip(zipPath); + const result = await parser.parseZip(zipPath); expect(result.verbs.size).toBe(3); expect(result.verbs.get('go')).toContain('went'); } finally { @@ -172,17 +172,17 @@ describe('Grid3VerbsParser - External morphology data', () => { // These tests run when GRID3_MORPHOLOGY_DIR points to a valid // Grid 3 Locale directory (or a directory someone has copied // the verbs.zip files into). On CI without Grid 3, these skip. - test('parses en-GB verbs.zip', () => { + test('parses en-GB verbs.zip', async () => { const zipPath = getMorphZip('en-GB'); if (!fileExists(zipPath)) return; - const result = parser.parseZip(zipPath); + const result = await parser.parseZip(zipPath); expect(result.verbs.size).toBeGreaterThan(100); }); - test('en-GB go -> goes, going, gone, went', () => { + test('en-GB go -> goes, going, gone, went', async () => { const zipPath = getMorphZip('en-GB'); if (!fileExists(zipPath)) return; - const result = parser.parseZip(zipPath); + const result = await parser.parseZip(zipPath); const goForms = result.verbs.get('go'); expect(goForms).toBeDefined(); expect(goForms).toContain('goes'); @@ -191,10 +191,10 @@ describe('Grid3VerbsParser - External morphology data', () => { expect(goForms).toContain('went'); }); - test('en-GB detailed has conditions', () => { + test('en-GB detailed has conditions', async () => { const zipPath = getMorphZip('en-GB'); if (!fileExists(zipPath)) return; - const result = parser.parseZipDetailed(zipPath); + const result = await parser.parseZipDetailed(zipPath); const goForms = result.verbs.get('go'); expect(goForms).toBeDefined(); expect(goForms!.length).toBeGreaterThan(0); @@ -202,10 +202,10 @@ describe('Grid3VerbsParser - External morphology data', () => { expect(went).toBeDefined(); }); - test('parses nb-NO verbs.zip', () => { + test('parses nb-NO verbs.zip', async () => { const zipPath = getMorphZip('nb-NO'); if (!fileExists(zipPath)) return; - const result = parser.parseZip(zipPath); + const result = await parser.parseZip(zipPath); expect(result.verbs.size).toBeGreaterThan(100); }); }); diff --git a/test/scanningMetrics.test.ts b/test/scanningMetrics.test.ts index d826173..8a7126b 100644 --- a/test/scanningMetrics.test.ts +++ b/test/scanningMetrics.test.ts @@ -47,7 +47,7 @@ describe('Scanning Metrics', () => { tree.rootId = 'root'; const calculator = new MetricsCalculator(); - const result = calculator.analyze(tree); + const result = await calculator.analyze(tree); const btn3Metrics = result.buttons.find((b) => b.label === '3'); // Steps = 3 (buttons 1, 2, 3), Selections = 1 @@ -83,7 +83,7 @@ describe('Scanning Metrics', () => { tree.addPage(page); const calculator = new MetricsCalculator(); - const result = calculator.analyze(tree); + const result = await calculator.analyze(tree); const metrics = result.buttons.find((b) => b.label === 'Target'); // Steps = (rowIndex + 1) + (colIndex + 1) = (2 + 1) + (3 + 1) = 7 steps @@ -141,7 +141,7 @@ describe('Scanning Metrics', () => { tree.addPage(page); const calculator = new MetricsCalculator(); - const result = calculator.analyze(tree); + const result = await calculator.analyze(tree); const metrics = result.buttons.find((b) => b.label === 'B2'); // Steps = blockOrder (2) + btnInBlockIndex (1) + 1 = 4 steps @@ -172,7 +172,7 @@ describe('Scanning Metrics', () => { tree.addPage(page); const calculator = new MetricsCalculator(); - const result = calculator.analyze(tree); + const result = await calculator.analyze(tree); const metrics = result.buttons.find((b) => b.label === 'B1'); // B1 is at root, index 0. diff --git a/test/suggestWordsEffort.test.ts b/test/suggestWordsEffort.test.ts index f3a8934..e7ac1d3 100644 --- a/test/suggestWordsEffort.test.ts +++ b/test/suggestWordsEffort.test.ts @@ -36,12 +36,12 @@ function buildTreeWithPredictions( } describe('Suggest Words effort cost', () => { - it('adds confirmation cost to Suggest Words word forms', () => { + it('adds confirmation cost to Suggest Words word forms', async () => { const suggestWords = ['something', 'someone', 'somewhere']; const { tree } = buildTreeWithPredictions(suggestWords, suggestWords); const calculator = new MetricsCalculator(); - const result = calculator.analyze(tree, { useSmartGrammar: true }); + const result = await calculator.analyze(tree, { useSmartGrammar: true }); const parentBtn = result.buttons.find((b) => b.label === 'some'); expect(parentBtn).toBeDefined(); @@ -56,12 +56,12 @@ describe('Suggest Words effort cost', () => { expect(something!.effort).toBeCloseTo(expectedEffort, 4); }); - it('does not add confirmation cost to morphology word forms', () => { + it('does not add confirmation cost to morphology word forms', async () => { const predictions = ['goes', 'going', 'went']; const { tree } = buildTreeWithPredictions(predictions, undefined, 'Verb'); const calculator = new MetricsCalculator(); - const result = calculator.analyze(tree, { useSmartGrammar: true }); + const result = await calculator.analyze(tree, { useSmartGrammar: true }); const parentBtn = result.buttons.find((b) => b.label === 'some'); expect(parentBtn).toBeDefined(); @@ -74,13 +74,13 @@ describe('Suggest Words effort cost', () => { expect(goes!.effort).toBeCloseTo(parentBtn!.effort + visualScanEffort(0), 4); }); - it('only adds confirmation to Suggest Words forms when predictions are mixed', () => { + it('only adds confirmation to Suggest Words forms when predictions are mixed', async () => { const suggestWordsOriginals = ['something', 'someone']; const allPredictions = ['something', 'someone', 'somes']; const { tree } = buildTreeWithPredictions(allPredictions, suggestWordsOriginals, 'Noun'); const calculator = new MetricsCalculator(); - const result = calculator.analyze(tree, { useSmartGrammar: true }); + const result = await calculator.analyze(tree, { useSmartGrammar: true }); const parentBtn = result.buttons.find((b) => b.label === 'some'); @@ -99,12 +99,12 @@ describe('Suggest Words effort cost', () => { expect(somes!.effort).toBeCloseTo(parentBtn!.effort + visualScanEffort(2), 4); }); - it('has no confirmation when parameters.predictions is absent', () => { + it('has no confirmation when parameters.predictions is absent', async () => { const predictions = ['something', 'someone']; const { tree } = buildTreeWithPredictions(predictions, undefined, 'Noun'); const calculator = new MetricsCalculator(); - const result = calculator.analyze(tree, { useSmartGrammar: true }); + const result = await calculator.analyze(tree, { useSmartGrammar: true }); const parentBtn = result.buttons.find((b) => b.label === 'some'); diff --git a/test/vocabularyDump.test.ts b/test/vocabularyDump.test.ts index 0d9023c..55c8798 100644 --- a/test/vocabularyDump.test.ts +++ b/test/vocabularyDump.test.ts @@ -183,7 +183,7 @@ describe('dumpVocabulary on example.gridset', () => { const processor = new GridsetProcessor(); const tree = await processor.loadIntoTree(assetPath); - const metrics = new MetricsCalculator().analyze(tree); + const metrics = await new MetricsCalculator().analyze(tree); const dump = dumpVocabulary(tree, { metrics }); const s = dump.summary;