Fix broken test suite and untrack runtime artifacts
- Bridge jsdom localStorage/sessionStorage over Node 22+'s experimental globals that vitest's jsdom environment doesn't override - Update Greek lookup tests to mock the OpenScriptures Strong's dictionary instead of the retired bolls.life path - Update migrateChunk no-op test for backfilled fields - Match en-dash verse range labels in chunk creation test - Untrack .api.log/.api.pid and gitignore them Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +0,0 @@
|
|||||||
SQLite database ready at /Users/nateemmert/Documents/study-app/server/data/projects.db
|
|
||||||
Bible Study API running on http://localhost:3001
|
|
||||||
@@ -7,3 +7,5 @@ coverage/
|
|||||||
*.local
|
*.local
|
||||||
.vite.pid
|
.vite.pid
|
||||||
.vite.log
|
.vite.log
|
||||||
|
.api.pid
|
||||||
|
.api.log
|
||||||
|
|||||||
+20
-16
@@ -16,15 +16,15 @@ const mockChapterData = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockGreekDefinition = [
|
// Mirrors the OpenScriptures Strong's Greek dictionary format loaded from jsdelivr.
|
||||||
{
|
const mockGreekDict = {
|
||||||
topic: 'G4102',
|
G4102: {
|
||||||
lexeme: 'πίστις',
|
lemma: 'πίστις',
|
||||||
transliteration: 'pistis',
|
translit: 'pistis',
|
||||||
short_definition: 'faith, belief',
|
kjv_def: 'faith, belief',
|
||||||
definition: '<p>Part(s) of speech: Noun</p><p>Faith or belief.</p>',
|
strongs_def: 'persuasion, i.e. credence; moral conviction',
|
||||||
},
|
},
|
||||||
];
|
};
|
||||||
|
|
||||||
function buildFetchMock({ chapterData = mockChapterData, greekData = null } = {}) {
|
function buildFetchMock({ chapterData = mockChapterData, greekData = null } = {}) {
|
||||||
return vi.fn((url) => {
|
return vi.fn((url) => {
|
||||||
@@ -34,8 +34,11 @@ function buildFetchMock({ chapterData = mockChapterData, greekData = null } = {}
|
|||||||
if (url.includes('bible.helloao.org')) {
|
if (url.includes('bible.helloao.org')) {
|
||||||
return Promise.resolve({ ok: true, json: () => Promise.resolve(chapterData) });
|
return Promise.resolve({ ok: true, json: () => Promise.resolve(chapterData) });
|
||||||
}
|
}
|
||||||
if (url.includes('bolls.life') && greekData !== null) {
|
if (url.includes('strongs-greek-dictionary') && greekData !== null) {
|
||||||
return Promise.resolve({ ok: true, json: () => Promise.resolve(greekData) });
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
text: () => Promise.resolve(`var strongsGreekDictionary = ${JSON.stringify(greekData)};`),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve({ ok: false });
|
return Promise.resolve({ ok: false });
|
||||||
});
|
});
|
||||||
@@ -45,10 +48,11 @@ function buildFetchMock({ chapterData = mockChapterData, greekData = null } = {}
|
|||||||
// Test lifecycle
|
// Test lifecycle
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.stubGlobal('fetch', buildFetchMock());
|
|
||||||
vi.stubGlobal('URL', { createObjectURL: vi.fn(() => 'blob:mock'), revokeObjectURL: vi.fn() });
|
|
||||||
vi.spyOn(window, 'confirm').mockReturnValue(false);
|
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
vi.stubGlobal('fetch', buildFetchMock());
|
||||||
|
vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock');
|
||||||
|
vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {});
|
||||||
|
vi.spyOn(window, 'confirm').mockReturnValue(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -182,7 +186,7 @@ describe('Chunk management', () => {
|
|||||||
test('creates a chunk by clicking a verse range', async () => {
|
test('creates a chunk by clicking a verse range', async () => {
|
||||||
await loadChapter();
|
await loadChapter();
|
||||||
addChunk('Paul, a servant', 'Grace and peace');
|
addChunk('Paul, a servant', 'Grace and peace');
|
||||||
await screen.findByText(/1-3/);
|
await screen.findByText(/1[-–]3/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('chunk count increments after each addition', async () => {
|
test('chunk count increments after each addition', async () => {
|
||||||
@@ -299,7 +303,7 @@ describe('Greek word lookup', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('populates fields after a successful lookup', async () => {
|
test('populates fields after a successful lookup', async () => {
|
||||||
await goToStudyAndAddGreekWord(buildFetchMock({ greekData: mockGreekDefinition }));
|
await goToStudyAndAddGreekWord(buildFetchMock({ greekData: mockGreekDict }));
|
||||||
fireEvent.change(screen.getByPlaceholderText(/G4102, H7225, 4102/i), { target: { value: 'G4102' } });
|
fireEvent.change(screen.getByPlaceholderText(/G4102, H7225, 4102/i), { target: { value: 'G4102' } });
|
||||||
fireEvent.click(screen.getByRole('button', { name: /look up greek/i }));
|
fireEvent.click(screen.getByRole('button', { name: /look up greek/i }));
|
||||||
await screen.findByDisplayValue('πίστις');
|
await screen.findByDisplayValue('πίστις');
|
||||||
@@ -308,7 +312,7 @@ describe('Greek word lookup', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('shows "No definition found." when the API returns an empty array', async () => {
|
test('shows "No definition found." when the API returns an empty array', async () => {
|
||||||
await goToStudyAndAddGreekWord(buildFetchMock({ greekData: [] }));
|
await goToStudyAndAddGreekWord(buildFetchMock({ greekData: {} }));
|
||||||
fireEvent.change(screen.getByPlaceholderText(/G4102, H7225, 4102/i), { target: { value: 'G4102' } });
|
fireEvent.change(screen.getByPlaceholderText(/G4102, H7225, 4102/i), { target: { value: 'G4102' } });
|
||||||
fireEvent.click(screen.getByRole('button', { name: /look up greek/i }));
|
fireEvent.click(screen.getByRole('button', { name: /look up greek/i }));
|
||||||
await screen.findByDisplayValue('No definition found.');
|
await screen.findByDisplayValue('No definition found.');
|
||||||
|
|||||||
@@ -1 +1,18 @@
|
|||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
|
// Node 22+ defines experimental localStorage/sessionStorage globals that are
|
||||||
|
// undefined unless --localstorage-file is passed. Vitest's jsdom environment
|
||||||
|
// skips copying window keys that already exist on the Node global, so jsdom's
|
||||||
|
// storage objects get shadowed. Bridge them from the raw jsdom instance.
|
||||||
|
const jsdomWindow = globalThis.jsdom?.window;
|
||||||
|
if (jsdomWindow) {
|
||||||
|
for (const key of ['localStorage', 'sessionStorage']) {
|
||||||
|
if (typeof globalThis[key] === 'undefined' && jsdomWindow[key]) {
|
||||||
|
Object.defineProperty(globalThis, key, {
|
||||||
|
value: jsdomWindow[key],
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+9
-2
@@ -263,9 +263,16 @@ describe('migrateChunk', () => {
|
|||||||
expect(result.crossReferences).toEqual([]);
|
expect(result.crossReferences).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('is a no-op when chunk already has observation field', () => {
|
test('preserves existing fields and backfills new ones when chunk already has observation field', () => {
|
||||||
const modern = { id: 'c1', startVerse: 1, endVerse: 1, observation: 'Already migrated.', interpretation: '', application: '', crossReferences: [], greekWords: [] };
|
const modern = { id: 'c1', startVerse: 1, endVerse: 1, observation: 'Already migrated.', interpretation: '', application: '', crossReferences: [], greekWords: [] };
|
||||||
expect(migrateChunk(modern)).toBe(modern);
|
const result = migrateChunk(modern);
|
||||||
|
expect(result).toMatchObject(modern);
|
||||||
|
expect(result.tags).toEqual([]);
|
||||||
|
expect(result.spilloverEndVerse).toBeNull();
|
||||||
|
expect(result.generalNotes).toBe('');
|
||||||
|
expect(result.episodeNumber).toBe('');
|
||||||
|
expect(result.episodeTitle).toBe('');
|
||||||
|
expect(result.finalScript).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles missing notes gracefully', () => {
|
test('handles missing notes gracefully', () => {
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
|
environmentOptions: {
|
||||||
|
jsdom: { url: 'http://localhost:3000/' },
|
||||||
|
},
|
||||||
globals: true,
|
globals: true,
|
||||||
setupFiles: './src/test-setup.js',
|
setupFiles: './src/test-setup.js',
|
||||||
coverage: {
|
coverage: {
|
||||||
|
|||||||
Reference in New Issue
Block a user