Skip to content

Commit a2d69e2

Browse files
committed
Refactor entry-edit-persists tests and page and component models
1 parent f475e89 commit a2d69e2

6 files changed

Lines changed: 235 additions & 199 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {type Page} from '@playwright/test';
2+
import {waitForProjectViewReady} from './test-utils';
3+
import {EntriesListComponent} from './entries-list-component';
4+
import {EntryViewComponent} from './entry-view-component';
5+
import {EntryApiHelper} from './entry-api-helper';
6+
7+
/**
8+
* Page object for the browse page which contains:
9+
* - EntriesListComponent (left panel - entry list with virtual scrolling)
10+
* - EntryViewComponent (right panel - entry detail/edit view)
11+
*/
12+
export class BrowsePage {
13+
readonly entriesList: EntriesListComponent;
14+
readonly entryView: EntryViewComponent;
15+
readonly api: EntryApiHelper;
16+
17+
constructor(readonly page: Page) {
18+
this.api = new EntryApiHelper(page);
19+
this.entriesList = new EntriesListComponent(page, this.api);
20+
this.entryView = new EntryViewComponent(page);
21+
}
22+
23+
async goto(): Promise<void> {
24+
await this.page.goto('/testing/project-view');
25+
await waitForProjectViewReady(this.page, true);
26+
}
27+
28+
async selectEntryByFilter(filter: string): Promise<void> {
29+
await this.entriesList.filterByText(filter);
30+
await this.entriesList.entryWithText(filter.slice(0, 5)).click();
31+
await this.entryView.waitForEntryLoaded();
32+
}
33+
}

frontend/viewer/tests/entries-list-page.ts renamed to frontend/viewer/tests/entries-list-component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import {waitForProjectViewReady} from './test-utils';
33
import type {EntryApiHelper} from './entry-api-helper';
44

55
/**
6-
* Page object for EntriesList tests.
6+
* Component object for EntriesList.
77
* Provides locators and common actions for virtual scrolling entry list.
88
*/
9-
export class EntriesListPage {
9+
export class EntriesListComponent {
1010
readonly vlist: Locator;
1111
readonly entryRows: Locator;
1212
readonly skeletons: Locator;

frontend/viewer/tests/entries-list.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import {expect, test} from '@playwright/test';
2-
import {EntriesListPage} from './entries-list-page';
2+
import {EntriesListComponent} from './entries-list-component';
33
import {EntryApiHelper} from './entry-api-helper';
44

55
const ESTIMATED_ITEM_HEIGHT = 60;
66
const BATCH_SIZE = 50;
77

88
test.describe('EntriesList', () => {
9-
let entriesList: EntriesListPage;
9+
let entriesList: EntriesListComponent;
1010
let api: EntryApiHelper;
1111

1212
test.describe('Lazy loading', () => {
1313
test.beforeEach(async ({page}) => {
1414
api = new EntryApiHelper(page);
15-
entriesList = new EntriesListPage(page, api);
15+
entriesList = new EntriesListComponent(page, api);
1616
await entriesList.goto();
1717
});
1818

@@ -85,7 +85,7 @@ test.describe('EntriesList', () => {
8585
test.describe('Jump to entry', () => {
8686
test.beforeEach(async ({page}) => {
8787
api = new EntryApiHelper(page);
88-
entriesList = new EntriesListPage(page, api);
88+
entriesList = new EntriesListComponent(page, api);
8989
await entriesList.goto();
9090
});
9191

@@ -137,15 +137,15 @@ test.describe('EntriesList', () => {
137137
test.describe('Entry event handling', () => {
138138
test.beforeEach(async ({page}) => {
139139
api = new EntryApiHelper(page);
140-
entriesList = new EntriesListPage(page, api);
140+
entriesList = new EntriesListComponent(page, api);
141141
await entriesList.goto(true);
142142
});
143143

144144
test('entry delete event removes entry from list without full reset', async () => {
145145
const initialTexts = await entriesList.getVisibleEntryTexts();
146146
expect(initialTexts.length).toBeGreaterThan(2);
147147

148-
const {id: firstEntryId} = await api.getEntryAtIndex(0);
148+
const firstEntryId = await api.getEntryIdAtIndex(0);
149149
await api.deleteEntry(firstEntryId);
150150

151151
await expect(async () => {
@@ -175,7 +175,7 @@ test.describe('EntriesList', () => {
175175
await entriesList.entryRows.first().click();
176176
await expect(entriesList.selectedEntry).toBeVisible();
177177

178-
const {id: entryId} = await api.getEntryAtIndex(0);
178+
const entryId = await api.getEntryIdAtIndex(0);
179179
await api.deleteEntry(entryId);
180180

181181
await entriesList.page.waitForTimeout(300);
@@ -191,7 +191,7 @@ test.describe('EntriesList', () => {
191191
expect(visibleTexts.length).toBeGreaterThan(0);
192192

193193
// Delete entry from top (not in cache)
194-
const {id: topEntryId} = await api.getEntryAtIndex(0);
194+
const topEntryId = await api.getEntryIdAtIndex(0);
195195
await api.deleteEntry(topEntryId);
196196

197197
await entriesList.page.waitForTimeout(500);

frontend/viewer/tests/entry-api-helper.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ export class EntryApiHelper {
1717
});
1818
}
1919

20-
async getEntryAtIndex(index: number): Promise<{id: string; headword: string}> {
20+
async getEntryAtIndex(index: number): Promise<{entryId: string; headword: string; senseCount: number}> {
2121
return this.page.evaluate(async ({idx, order}) => {
2222
const api = window.__PLAYWRIGHT_UTILS__.demoApi;
2323
const entries = await api.getEntries({offset: idx, count: 1, order});
2424
const entry = entries[0];
2525
return {
26-
id: entry.id,
26+
entryId: entry.id,
2727
headword: entry.citationForm?.seh ?? entry.lexemeForm?.seh ?? '',
28+
senseCount: entry.senses.length,
2829
};
2930
}, {idx: index, order: DEFAULT_ORDER});
3031
}
@@ -34,6 +35,11 @@ export class EntryApiHelper {
3435
return headword;
3536
}
3637

38+
async getEntryIdAtIndex(index: number): Promise<string> {
39+
const {entryId} = await this.getEntryAtIndex(index);
40+
return entryId;
41+
}
42+
3743
async getLastEntry(): Promise<{headword: string; index: number}> {
3844
return this.page.evaluate(async ({order}) => {
3945
const api = window.__PLAYWRIGHT_UTILS__.demoApi;
@@ -139,4 +145,58 @@ export class EntryApiHelper {
139145
return {id: entry.id, updatedHeadword: newHeadword};
140146
}, {idx: index, pfx: prefix, order: DEFAULT_ORDER});
141147
}
148+
149+
async getEntryWithEnglishGloss(): Promise<{entryId: string; headword: string; originalGloss: string}> {
150+
return this.page.evaluate(async ({order}) => {
151+
const api = window.__PLAYWRIGHT_UTILS__.demoApi;
152+
const entries = await api.getEntries({offset: 0, count: 50, order});
153+
const entry = entries.find(e => e.senses.length > 0 && e.senses[0].gloss?.en);
154+
if (!entry) throw new Error('No suitable entry with English gloss found in demo data');
155+
return {
156+
entryId: entry.id,
157+
headword: entry.citationForm?.seh ?? entry.lexemeForm?.seh ?? '',
158+
originalGloss: entry.senses[0].gloss?.en ?? '',
159+
};
160+
}, {order: DEFAULT_ORDER});
161+
}
162+
163+
async getEntry(entryId: string): Promise<unknown> {
164+
return this.page.evaluate(async (id) => {
165+
return window.__PLAYWRIGHT_UTILS__.demoApi.getEntry(id);
166+
}, entryId);
167+
}
168+
169+
async getEntryGloss(entryId: string, writingSystem: string): Promise<string> {
170+
return this.page.evaluate(async ({id, ws}) => {
171+
const api = window.__PLAYWRIGHT_UTILS__.demoApi;
172+
const entry = await api.getEntry(id);
173+
return entry?.senses[0]?.gloss?.[ws] ?? '';
174+
}, {id: entryId, ws: writingSystem});
175+
}
176+
177+
async getEntryLexeme(entryId: string): Promise<string> {
178+
return this.page.evaluate(async (id) => {
179+
const api = window.__PLAYWRIGHT_UTILS__.demoApi;
180+
const entry = await api.getEntry(id);
181+
return entry?.lexemeForm?.seh ?? '';
182+
}, entryId);
183+
}
184+
185+
async getEntrySenseCount(entryId: string): Promise<number> {
186+
return this.page.evaluate(async (id) => {
187+
const api = window.__PLAYWRIGHT_UTILS__.demoApi;
188+
const entry = await api.getEntry(id);
189+
return entry?.senses.length ?? 0;
190+
}, entryId);
191+
}
192+
193+
async entryHasGlossValue(entryId: string, glossValue: string): Promise<boolean> {
194+
return this.page.evaluate(async ({id, value}) => {
195+
const api = window.__PLAYWRIGHT_UTILS__.demoApi;
196+
const entry = await api.getEntry(id);
197+
return entry?.senses.some(s =>
198+
Object.values(s.gloss || {}).some(v => v === value)
199+
) ?? false;
200+
}, {id: entryId, value: glossValue});
201+
}
142202
}

0 commit comments

Comments
 (0)