diff --git a/apps/web/e2e/home.spec.ts b/apps/web/e2e/home.spec.ts index 0dd7970..793224e 100644 --- a/apps/web/e2e/home.spec.ts +++ b/apps/web/e2e/home.spec.ts @@ -1,3 +1,5 @@ +import type { Page } from '@playwright/test'; + import { home } from './demo'; import { expect, test } from './fixtures'; @@ -25,6 +27,35 @@ test('the Home leaves out This week when its answer has no week', async ({ page await expect(page.locator('main section').last()).toContainText('Your apps'); }); +// The colour --ink-muted gives text in the page's theme. +function inkMuted(page: Page) { + return page.evaluate(() => { + const probe = document.body.appendChild(document.createElement('span')); + probe.style.color = 'var(--ink-muted)'; + const { color } = getComputedStyle(probe); + probe.remove(); + return color; + }); +} + +test("the Home says the hours are unavailable when home couldn't get the week", async ({ page }, testInfo) => { + await page.route('/api/home', (route) => + route.fulfill({ json: { ...home, week: undefined, weekState: 'unavailable' } }) + ); + await page.goto('/'); + + const week = page.locator('main section').last(); + await expect(week.getByRole('heading', { level: 2 })).toHaveText('This week'); + await expect(week.getByRole('link', { name: 'Day report' })).toHaveAttribute('href', '/time'); + // In place of the hours, in their type and muted, without the label or the bar (decision 187). + const hours = week.getByText('Hours unavailable'); + await expect(hours).toHaveCSS('font-size', testInfo.project.name === 'phone' ? '24px' : '28px'); + await expect(hours).toHaveCSS('font-weight', '500'); + await expect(hours).toHaveCSS('color', await inkMuted(page)); + await expect(week.getByText('Logged on Jira')).toHaveCount(0); + await expect(week.locator('.track')).toHaveCount(0); +}); + test('without a session, every page shows the Signed out screen', async ({ page }) => { await page.route('/api/home', (route) => route.fulfill({ status: 401 })); await page.goto('/'); diff --git a/apps/web/src/home/HomePage.svelte b/apps/web/src/home/HomePage.svelte index 70c1885..c09020b 100644 --- a/apps/web/src/home/HomePage.svelte +++ b/apps/web/src/home/HomePage.svelte @@ -5,7 +5,8 @@ import NeedsYou from './NeedsYou.svelte'; import ThisWeek from './ThisWeek.svelte'; import YourApps from './YourApps.svelte'; -// Until the Home first loads, only the greeting and the apps show; the week, only when the Home has one. +// Until the Home first loads, only the greeting and the apps show; the week, when the Home has one, and its +// head with "Hours unavailable" when home couldn't get it from the time service (decision 187). let { now, home }: { now: Date; home?: Home } = $props(); @@ -16,4 +17,6 @@ let { now, home }: { now: Date; home?: Home } = $props(); {#if home?.week} +{:else if home?.weekState === 'unavailable'} + {/if} diff --git a/apps/web/src/home/HomePage.test.ts b/apps/web/src/home/HomePage.test.ts index 7031b10..26a7a2c 100644 --- a/apps/web/src/home/HomePage.test.ts +++ b/apps/web/src/home/HomePage.test.ts @@ -14,6 +14,9 @@ const withoutWeek: Home = { const withWeek: Home = { ...withoutWeek, week: { loggedHours: 31.5, targetHours: 40 } }; +// home asked the time service for the week, and couldn't get it. +const unavailable: Home = { ...withoutWeek, weekState: 'unavailable' }; + // The week's head, its link and its hours; null for each that doesn't show. function weekParts(): (HTMLElement | null)[] { return [ @@ -74,3 +77,52 @@ test('shows no week before the Home first loads', () => { expect(weekParts()).toEqual([null, null, null]); expect(screen.getByRole('heading', { level: 2, name: 'Your apps' })).toBeInTheDocument(); }); + +test("says the hours are unavailable when home couldn't get the week, under its head and link, after the apps", () => { + render(HomePage, { props: { now, home: unavailable } }); + + const [head, link, hours] = weekParts(); + expect(head).toBeInTheDocument(); + expect(link).toHaveAttribute('href', '/time'); + expect(hours).toBeNull(); + expect(screen.getByText('Hours unavailable')).toBeInTheDocument(); + const heads = screen.getAllByRole('heading', { level: 2 }).map((heading) => heading.textContent); + expect(heads).toEqual(['Needs you today', 'Your apps', 'This week']); +}); + +test('shows the week when the Home has one, whatever its state says', () => { + render(HomePage, { props: { now, home: { ...withWeek, weekState: 'unavailable' } } }); + + expect(weekParts()).not.toContain(null); + expect(screen.queryByText('Hours unavailable')).not.toBeInTheDocument(); +}); + +test('leaves out the week when the time service had none to give, as without Jira', () => { + render(HomePage, { props: { now, home: { ...withoutWeek, weekState: 'ok' } } }); + + expect(weekParts()).toEqual([null, null, null]); + expect(screen.queryByText('Hours unavailable')).not.toBeInTheDocument(); +}); + +test('follows each later Home: from the hours to unavailable, to no week, and back', async () => { + const { rerender } = render(HomePage, { props: { now, home: withWeek } }); + + await rerender({ home: unavailable }); + + expect(screen.getByText('Hours unavailable')).toBeInTheDocument(); + expect(screen.queryByText('31h 30m / 40h')).not.toBeInTheDocument(); + + await rerender({ home: withoutWeek }); + + expect(weekParts()).toEqual([null, null, null]); + expect(screen.queryByText('Hours unavailable')).not.toBeInTheDocument(); + + await rerender({ home: unavailable }); + + expect(screen.getByText('Hours unavailable')).toBeInTheDocument(); + + await rerender({ home: withWeek }); + + expect(weekParts()).not.toContain(null); + expect(screen.queryByText('Hours unavailable')).not.toBeInTheDocument(); +}); diff --git a/apps/web/src/home/ThisWeek.svelte b/apps/web/src/home/ThisWeek.svelte index 2013b1b..ab919c4 100644 --- a/apps/web/src/home/ThisWeek.svelte +++ b/apps/web/src/home/ThisWeek.svelte @@ -4,7 +4,7 @@ import Section from '../shell/Section.svelte'; import WeekCard from '../time/WeekCard.svelte'; import type { Week } from '../time/week'; -let { week }: { week: Week } = $props(); +let { week }: { week?: Week } = $props();
diff --git a/apps/web/src/home/ThisWeek.test.ts b/apps/web/src/home/ThisWeek.test.ts index ba3ffd2..541dce7 100644 --- a/apps/web/src/home/ThisWeek.test.ts +++ b/apps/web/src/home/ThisWeek.test.ts @@ -10,3 +10,11 @@ test("shows the week's card under its head, which links to the day report", () = expect(screen.getByRole('link', { name: 'Day report' })).toHaveAttribute('href', '/time'); expect(screen.getByText('30h / 40h')).toBeInTheDocument(); }); + +test('without a week, keeps its head and link, and its card says the hours are unavailable', () => { + render(ThisWeek); + + expect(screen.getByRole('heading', { level: 2, name: 'This week' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'Day report' })).toHaveAttribute('href', '/time'); + expect(screen.getByText('Hours unavailable')).toBeInTheDocument(); +}); diff --git a/apps/web/src/home/api.ts b/apps/web/src/home/api.ts index 8364c55..ff442eb 100644 --- a/apps/web/src/home/api.ts +++ b/apps/web/src/home/api.ts @@ -18,9 +18,12 @@ export interface Item { late: boolean; } +/** How home's asking went: the peer answered, or it failed or didn't answer in time. */ +type State = 'ok' | 'unavailable'; + /** How asking one of home's sources went, with only the field the app reads so far. */ interface SourceStatus { - state: 'ok' | 'unavailable'; + state: State; } /** The Home screen's data from GET /api/home, with only the fields the app reads so far. */ @@ -29,8 +32,10 @@ export interface Home { items: Item[]; /** One per source home asks; absent from the answer of a home older than the field. */ sources?: SourceStatus[]; - /** Absent when home has no week, such as before the time service logs hours. */ + /** Absent when home has no week: the time service has none, as without Jira, couldn't give one, or isn't there. */ week?: Week; + /** How asking the time service for the week went; absent without one to ask, or from a home older than the field. */ + weekState?: State; } /** The error fetchHome fails with when the gateway answers 401: the user has no session. */ diff --git a/apps/web/src/time/WeekCard.svelte b/apps/web/src/time/WeekCard.svelte index 6f0e122..f6f64dc 100644 --- a/apps/web/src/time/WeekCard.svelte +++ b/apps/web/src/time/WeekCard.svelte @@ -1,18 +1,24 @@
-

- {hoursLabel(week)} - Logged on Jira -

- - + {#if week} +

+ {hoursLabel(week)} + Logged on Jira +

+ + + {:else} +

Hours unavailable

+ {/if}