Skip to content

Commit

Permalink
Navigation: Start writing unit tests for TopBarTracker
Browse files Browse the repository at this point in the history
And fix some bugs that they caught.

I still need to add more tests
  • Loading branch information
SLaks committed Oct 22, 2024
1 parent 05360be commit ca53a62
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 34 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"js": true,
"ts": "module"
},
"timeout": "30s",
"files": [
"src/**/*.test.*"
],
Expand Down
149 changes: 141 additions & 8 deletions src/view-model/navigation/top-bar-model.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import test from 'ava'
import { ScrollViewModel } from '../scroll-view-model.ts'
import test, { ExecutionContext } from 'ava'
import {
RenderedEntry,
RenderedLineInfo,
ScrollViewModel,
} from '../scroll-view-model.ts'
import { LeiningGenerator } from '../../calendar-model/generator.ts'
import { UserSettings } from '../../calendar-model/user-settings.ts'
import { fetchPages, renderLine } from '../test-utils.ts'
import { Link, TopBarTracker } from './top-bar-model.ts'

const testSettings: UserSettings = {
ashkenazi: true,
Expand All @@ -11,11 +17,138 @@ const testSettings: UserSettings = {

const generator = new LeiningGenerator(testSettings)

test('renders for בראשית', (t) => {
const viewModel = ScrollViewModel.forId(
generator,
'2024-10-26:shacharis,main'
const cachedTracker = new TopBarTracker()

let viewModel: ScrollViewModel | null = null
let pages: RenderedEntry[] = []

async function createModel(
id: string,
fetch: Parameters<typeof fetchPages>[1]
) {
viewModel = ScrollViewModel.forId(generator, id)
pages = await fetchPages(viewModel, fetch)
}

test.afterEach('reset state', () => {
viewModel = null
pages = []
})

test.serial('renders for the beginning of בראשית', async (t) => {
await createModel('2024-10-26:shacharis,main', {
count: 5,
fetchPreviousPages: false,
})

t.snapshot(
renderResult(t, {
first: firstLine(pages[0]),
center: null,
last: firstLine(pages[1]),
})
)
})

test.serial('renders for שמחת תורה', async (t) => {
await createModel('2024-10-25:shacharis,main', {
count: 5,
fetchPreviousPages: false,
})

t.snapshot(
renderResult(t, {
first: firstLine(pages[0]),
center: null,
last: firstLine(pages[1]),
})
)
})

test.serial('שקלים / ראש חודש as פרשה', async (t) => {
await createModel('2025-03-01:shacharis,main', {
count: 5,
fetchPreviousPages: false,
})

t.snapshot(
renderResult(t, {
first: getLine(
'וַיְדַבֵּ֥ר יְהֹוָ֖ה אֶל־מֹשֶׁ֥ה לֵּאמֹֽר׃ דַּבֵּר֙ אֶל־בְּנֵ֣י יִשְׂרָאֵ֔ל'
),
center: null,
last: getLine(
'זָהָ֣ב טָה֑וֹר אַמָּתַ֤יִם וָחֵ֙צִי֙ אׇרְכָּ֔הּ וְאַמָּ֥ה וָחֵ֖צִי רׇחְבָּֽהּ׃'
),
})
)
// TODO: Snapshot
t.is(viewModel, viewModel)
})

test.serial('renders for חול המועד סוכות', async (t) => {
await createModel('2024-10-22:shacharis,main', {
count: 5,
fetchPreviousPages: false,
})

t.snapshot(
renderResult(t, {
first: getLine(
'הַחֲמִישִׁ֛י פָּרִ֥ים תִּשְׁעָ֖ה אֵילִ֣ם שְׁנָ֑יִם כְּבָשִׂ֧ים בְּנֵֽי'
),
center: getLine(
'שְׁמֹנָ֖ה אֵילִ֣ם שְׁנָ֑יִם כְּבָשִׂ֧ים בְּנֵי־שָׁנָ֛ה אַרְבָּעָ֥ה עָשָׂ֖ר'
),
last: getLine(
'וּשְׂעִ֥יר חַטָּ֖את אֶחָ֑ד מִלְּבַד֙ עֹלַ֣ת הַתָּמִ֔יד מִנְחָתָ֖הּ'
),
})
)
})

function renderResult(
t: ExecutionContext,
lines: Parameters<TopBarTracker['setLine']>[1]
) {
if (!viewModel) throw new Error('Must create viewModel first')
const cachedResult = cachedTracker.setLine(viewModel, lines)
const freshResult = new TopBarTracker().setLine(viewModel, lines)
t.deepEqual(cachedResult, freshResult)
return {
aliyahRange: cachedResult.aliyahRange,
currentRun: cachedResult.currentRun?.id,
previousLink: renderLink(cachedResult.previousLink),
nextLink: renderLink(cachedResult.nextLink),
relatedRuns: cachedResult.relatedRuns.map(renderLink),
}
}

function renderLink(link: Link | null) {
if (!link) return link
return {
targetRun: link.targetRun.id,
label: link.label,
}
}

function firstLine(page: RenderedEntry) {
if (page.type !== 'page') throw new Error('Must be a page')
return page.lines[0]
}

/** Finds the single line containing the specified text. */
function getLine(text: string): RenderedLineInfo {
if (!viewModel) throw new Error('Must create viewModel first')
const results = pages.flatMap((p) =>
p.type === 'message'
? []
: p.lines.filter((line) => renderLine(line).includes(text))
)

if (results.length !== 1)
throw new Error(
`Found ${results.length} lines:
${results.map(renderLine).join('\n')}`.trim()
)
return results[0]
}
135 changes: 135 additions & 0 deletions src/view-model/navigation/top-bar-model.test.ts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Snapshot report for `src/view-model/navigation/top-bar-model.test.ts`

The actual snapshot is saved in `top-bar-model.test.ts.snap`.

Generated by [AVA](https://avajs.dev).

## renders for the beginning of בראשית

> Snapshot 1
{
aliyahRange: [
'ראשון',
],
currentRun: '2024-10-26:shacharis,main',
nextLink: {
label: 'ראש חודש חשון שחרית',
targetRun: '2024-11-01:shacharis,main',
},
previousLink: {
label: 'שמחת תורה שחרית',
targetRun: '2024-10-25:shacharis,main',
},
relatedRuns: [
{
label: 'פרשת בראשית',
targetRun: '2024-10-26:shacharis,main',
},
{
label: 'הפטרה',
targetRun: '2024-10-26:shacharis,haftara',
},
],
}

## renders for שמחת תורה

> Snapshot 1
{
aliyahRange: [
'רביעי',
],
currentRun: '2024-10-25:shacharis,main',
nextLink: {
label: 'פרשת בראשית שחרית',
targetRun: '2024-10-26:shacharis,main',
},
previousLink: {
label: 'שמיני עצרת מעריב',
targetRun: '2024-10-24:maariv,main',
},
relatedRuns: [
{
label: 'שמחת תורה',
targetRun: '2024-10-25:shacharis,main',
},
{
label: 'חתן בראשית',
targetRun: '2024-10-25:shacharis,last-aliyah',
},
{
label: 'מפטיר',
targetRun: '2024-10-25:shacharis,maftir',
},
{
label: 'הפטרה',
targetRun: '2024-10-25:shacharis,haftara',
},
],
}

## שקלים / ראש חודש as פרשה

> Snapshot 1
{
aliyahRange: [
'ראשון',
'שני',
],
currentRun: '2025-03-01:shacharis,main',
nextLink: {
label: 'פרשת תצוה שחרית',
targetRun: '2025-03-08:shacharis,main',
},
previousLink: {
label: 'ראש חודש אדר שחרית',
targetRun: '2025-02-28:shacharis,main',
},
relatedRuns: [
{
label: 'פרשת תרומה',
targetRun: '2025-03-01:shacharis,main',
},
{
label: 'שביעי',
targetRun: '2025-03-01:shacharis,last-aliyah',
},
{
label: 'מפטיר',
targetRun: '2025-03-01:shacharis,maftir',
},
{
label: 'הפטרה',
targetRun: '2025-03-01:shacharis,haftara',
},
],
}

## renders for חול המועד סוכות

> Snapshot 1
{
aliyahRange: [
'ראשון',
'שלישי',
],
currentRun: '2024-10-22:shacharis,main',
nextLink: {
label: 'סוכות ז׳ (הושענא רבה) שחרית',
targetRun: '2024-10-23:shacharis,main',
},
previousLink: {
label: 'סוכות חול המועד יום ג׳ שחרית',
targetRun: '2024-10-21:shacharis,main',
},
relatedRuns: [
{
label: 'סוכות חול המועד יום ד׳',
targetRun: '2024-10-22:shacharis,main',
},
],
}
Binary file not shown.
15 changes: 8 additions & 7 deletions src/view-model/navigation/top-bar-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {
LeiningAliyah,
LeiningInstance,
LeiningRun,
} from '../../calendar-model/model-types'
import { aliyahName } from '../aliyah-labeller'
import { RenderedLineInfo, ScrollViewModel } from '../scroll-view-model'
} from '../../calendar-model/model-types.ts'
import { aliyahName } from '../aliyah-labeller.ts'
import { RenderedLineInfo, ScrollViewModel } from '../scroll-view-model.ts'

/** The information and link targets displayed in the top navigation bar. */
export interface TopBarInfo {
Expand Down Expand Up @@ -48,7 +48,7 @@ export interface TopBarInfo {
relatedRuns: Link[]
}

interface Link {
export interface Link {
targetRun: LeiningRun
/** Will omit context if the same as the current run. */
label: string
Expand Down Expand Up @@ -98,7 +98,7 @@ export class TopBarTracker {
const lastAliyah = lines.last?.aliyot[0] ?? lines.center?.aliyot[0]

const run = lines.center?.run ?? lines.last?.run ?? lines.first?.run
if (!run) return
if (!run) return this.currentInfo
newInfo.currentRun = run

if (
Expand All @@ -108,7 +108,7 @@ export class TopBarTracker {
newInfo.relatedRuns = newInfo.currentRun.leining.runs.map((run) => ({
// Use aliyahName() to turn שביעי into חתן בראשית.
// Use `run.type` to label הפטרות.
label: aliyahName(run.aliyot[0].index, run) ?? run.type,
label: aliyahName(run.aliyot[0].index, run) || run.type,
targetRun: run,
}))

Expand Down Expand Up @@ -157,6 +157,7 @@ export class TopBarTracker {
}

this.currentInfo = newInfo
return this.currentInfo
}

private generateAliyahRange(currentRun: LeiningRun): string[] {
Expand All @@ -165,7 +166,7 @@ export class TopBarTracker {
? [this.firstAliyah]
: [this.firstAliyah, this.lastAliyah]
)
.filter((a) => a != null)
.filter((a): a is NonNullable<typeof a> => a != null)
.map((a) => {
// Pass isEnd to label ראשון instead of repeating the leining name.
const label = aliyahName(a.aliyah.index, a.run, { isEnd: true })
Expand Down
19 changes: 1 addition & 18 deletions src/view-model/scroll-view-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ScrollViewModel,
} from './scroll-view-model.ts'
import { last } from '../calendar-model/utils.ts'
import { renderLine } from './test-utils.ts'
import { fetchPages, renderLine } from './test-utils.ts'
import { containsRef } from '../calendar-model/ref-utils.ts'

const testSettings: UserSettings = {
Expand Down Expand Up @@ -307,23 +307,6 @@ function lineContains(line: RenderedLineInfo, range: number[] | undefined) {
return line.verses.some(({ c, v }) => c === range[0] && v === range[1])
}

async function fetchPages(
model: ScrollViewModel | null,
{ count, fetchPreviousPages }: { count: number; fetchPreviousPages: boolean }
): Promise<RenderedEntry[]> {
if (!model) throw new Error('No model')
const pages: RenderedEntry[] = [(await model.startingLocation).page]

if (fetchPreviousPages) count /= 2
for (let i = 0; i < count; i++) {
const previousPage = await model.fetchPreviousPage()
if (previousPage) pages.unshift(previousPage)
const nextPage = await model.fetchNextPage()
if (nextPage) pages.push(nextPage)
}
return pages
}

/** Renders enough properties of a scroll to make `deepEqual()` work with useful failures. */
async function renderScroll(model: ScrollViewModel | null) {
return (
Expand Down
Loading

0 comments on commit ca53a62

Please sign in to comment.