@blotch/dolan 22.2.3 → 22.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.js +9 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +36 -3
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +187 -7
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +280 -7
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +38 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +107 -1
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/dist/commands/update.d.ts +5 -0
- package/dist/commands/update.js +126 -45
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +314 -2
- package/dist/commands/update.test.js.map +1 -1
- package/dist/lockfile.d.ts +7 -0
- package/dist/lockfile.js +36 -0
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +25 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +48 -2
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +45 -3
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +5 -0
- package/dist/test-utils.js +6 -1
- package/dist/test-utils.js.map +1 -1
- package/dist/testing/fire-lynx-event.d.ts +14 -0
- package/dist/testing/fire-lynx-event.js +19 -0
- package/dist/testing/fire-lynx-event.js.map +1 -0
- package/dist/testing/fire-lynx-event.test.d.ts +1 -0
- package/dist/testing/fire-lynx-event.test.js +44 -0
- package/dist/testing/fire-lynx-event.test.js.map +1 -0
- package/dist/testing/lynx-main-thread.d.ts +22 -0
- package/dist/testing/lynx-main-thread.js +11 -0
- package/dist/testing/lynx-main-thread.js.map +1 -0
- package/dist/testing/lynx-main-thread.test.d.ts +1 -0
- package/dist/testing/lynx-main-thread.test.js +24 -0
- package/dist/testing/lynx-main-thread.test.js.map +1 -0
- package/dist/testing/set-input-signal.d.ts +5 -4
- package/dist/testing/set-input-signal.js +5 -4
- package/dist/testing/set-input-signal.js.map +1 -1
- package/dist/ui/components/accordion/accordion.test.ts +158 -144
- package/dist/ui/components/accordion/accordion.ts +19 -14
- package/dist/ui/components/action-sheet/action-sheet.test.ts +272 -187
- package/dist/ui/components/action-sheet/action-sheet.ts +40 -34
- package/dist/ui/components/alert/alert.test.ts +67 -19
- package/dist/ui/components/alert/alert.ts +14 -16
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +206 -139
- package/dist/ui/components/alert-dialog/alert-dialog.ts +32 -26
- package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
- package/dist/ui/components/avatar/avatar.test.ts +89 -45
- package/dist/ui/components/badge/badge.test.ts +57 -23
- package/dist/ui/components/badge/badge.ts +12 -14
- package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +373 -297
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +34 -33
- package/dist/ui/components/button/button.test.ts +611 -2
- package/dist/ui/components/button/button.ts +431 -62
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +484 -2
- package/dist/ui/components/button-group/button-group.ts +380 -68
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
- package/dist/ui/components/card/card.test.ts +82 -62
- package/dist/ui/components/chart/chart-presets.test.ts +197 -1
- package/dist/ui/components/chart/chart-presets.ts +8 -1
- package/dist/ui/components/chart/chart-theme.ts +12 -0
- package/dist/ui/components/chart/chart.test.ts +1017 -0
- package/dist/ui/components/chart/chart.ts +56 -43
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +23 -23
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +19 -14
- package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
- package/dist/ui/components/context-menu/context-menu.ts +110 -0
- package/dist/ui/components/context-menu/index.ts +1 -0
- package/dist/ui/components/dialog/dialog.test.ts +210 -152
- package/dist/ui/components/dialog/dialog.ts +29 -23
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +133 -0
- package/dist/ui/components/dock/dock.test.ts +749 -0
- package/dist/ui/components/dock/dock.ts +960 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
- package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
- package/dist/ui/components/glass/concentric-radius.ts +17 -0
- package/dist/ui/components/glass/glass-design.test.ts +78 -0
- package/dist/ui/components/glass/glass-design.ts +119 -0
- package/dist/ui/components/glass/glass.test.ts +338 -0
- package/dist/ui/components/glass/glass.ts +360 -0
- package/dist/ui/components/glass/index.ts +18 -0
- package/dist/ui/components/icon/icon-theme.test.ts +36 -0
- package/dist/ui/components/icon/icon-theme.ts +36 -13
- package/dist/ui/components/icon/icon.test.ts +52 -41
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +15 -12
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
- package/dist/ui/components/list/list.test.ts +80 -49
- package/dist/ui/components/menu/index.ts +25 -0
- package/dist/ui/components/menu/menu-placement.test.ts +153 -0
- package/dist/ui/components/menu/menu-placement.ts +159 -0
- package/dist/ui/components/menu/menu-popup.test.ts +632 -0
- package/dist/ui/components/menu/menu-popup.ts +544 -0
- package/dist/ui/components/menu/menu.test.ts +429 -0
- package/dist/ui/components/menu/menu.ts +415 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
- package/dist/ui/components/nav-drawer/nav-drawer.ts +72 -56
- package/dist/ui/components/pagination/pagination.test.ts +136 -86
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +52 -54
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +24 -24
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
- package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +17 -11
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +64 -11
- package/dist/ui/components/sheet/sheet.ts +9 -1
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/spinner/spinner.test.ts +64 -46
- package/dist/ui/components/spinner/spinner.ts +16 -19
- package/dist/ui/components/switch/switch.test.ts +116 -46
- package/dist/ui/components/switch/switch.ts +8 -13
- package/dist/ui/components/tab-bar/index.ts +1 -0
- package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
- package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
- package/dist/ui/components/tabs/tabs.test.ts +215 -157
- package/dist/ui/components/tabs/tabs.ts +110 -118
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +14 -12
- package/dist/ui/components/toast/toast.test.ts +434 -350
- package/dist/ui/components/toast/toast.ts +42 -51
- package/dist/ui/components/toggle/toggle.test.ts +247 -140
- package/dist/ui/theme/chart-colors.test.ts +8 -3
- package/dist/ui/theme/dark.css +38 -2
- package/dist/ui/theme/default.css +55 -7
- package/dist/ui/theme/icon-colors.test.ts +8 -3
- package/dist/ui/theme/rgb-channels.test.ts +84 -0
- package/dist/ui/theme/tailwind-build.test.ts +114 -0
- package/dist/ui/theme/tailwind-css.test.ts +83 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
- package/dist/ui/theme/tailwind-plugin.ts +93 -29
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +14 -0
- package/dist/utils/analyze.js +15 -0
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/analyze.test.js +14 -1
- package/dist/utils/analyze.test.js.map +1 -1
- package/dist/utils/installed.d.ts +12 -0
- package/dist/utils/installed.js +16 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +17 -1
- package/dist/utils/installed.test.js.map +1 -1
- package/dist/utils/resolve-paths.d.ts +4 -4
- package/dist/utils/resolve-paths.js +5 -7
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +9 -5
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +25 -0
- package/dist/utils/tailwind.js +51 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +71 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +9 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
- package/dist/ui/components/button/button-render.test.ts +0 -132
- package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
- package/dist/ui/components/chart/chart-render.test.ts +0 -730
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
|
@@ -40,10 +40,10 @@ import { UI_ICON_COLOR } from '../icon/icon-theme';
|
|
|
40
40
|
<!-- bg-black/50 dims the content behind the drawer. The fadeIn/fadeOut in
|
|
41
41
|
#animateIn/#animateOut already tween this element's opacity over
|
|
42
42
|
DURATION.normal, so the dim animates in lockstep with the panel slide —
|
|
43
|
-
matching the dialog/action-sheet/bottom-sheet backdrop.
|
|
44
|
-
|
|
45
|
-
rgb(0 0 0 / 0.5), which Lynx
|
|
46
|
-
|
|
43
|
+
matching the dialog/action-sheet/bottom-sheet backdrop. It's a named
|
|
44
|
+
color, not a theme one, because the dim is black in light and dark
|
|
45
|
+
mode alike; bg-black/50 compiles to rgb(0 0 0 / 0.5), which Lynx
|
|
46
|
+
accepts. -->
|
|
47
47
|
<view
|
|
48
48
|
#backdrop
|
|
49
49
|
class="h-full w-full bg-black/50"
|
|
@@ -82,8 +82,11 @@ export class UiNavDrawer implements OnDestroy {
|
|
|
82
82
|
: 'position: fixed; overflow: visible; width: 100vw; height: 100vh; top: 0; left: 0; z-index: 999; display: none;',
|
|
83
83
|
);
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Required: both are always in the template (the overlay hides with
|
|
86
|
+
// display:none rather than leaving the tree), and the sequences only read
|
|
87
|
+
// them from timers that start after the first render.
|
|
88
|
+
readonly backdropRef = viewChild.required<ElementRef>('backdrop');
|
|
89
|
+
readonly panelRef = viewChild.required<ElementRef>('panel');
|
|
87
90
|
#backdropAnim?: AnimationHandle;
|
|
88
91
|
#panelAnim?: AnimationHandle;
|
|
89
92
|
#hasBeenOpen = false;
|
|
@@ -91,21 +94,26 @@ export class UiNavDrawer implements OnDestroy {
|
|
|
91
94
|
// them.
|
|
92
95
|
readonly #timers = new Set<ReturnType<typeof setTimeout>>();
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Plays the open or close sequence whenever `open` changes. A field rather
|
|
99
|
+
* than a constructor: Angular's test build keeps a function for a
|
|
100
|
+
* constructor that nothing calls, which an app's coverage report would show
|
|
101
|
+
* as uncovered.
|
|
102
|
+
*
|
|
103
|
+
* `#hasBeenOpen` prevents the close animation from running on the initial
|
|
104
|
+
* effect evaluation when `open` starts as false. Without this guard, the
|
|
105
|
+
* first run would call #doClose() and emit `closed` before the drawer
|
|
106
|
+
* has ever been opened.
|
|
107
|
+
*/
|
|
108
|
+
protected readonly openAndClose = effect(() => {
|
|
109
|
+
const isOpen = this.open();
|
|
110
|
+
if (isOpen) {
|
|
111
|
+
this.#hasBeenOpen = true;
|
|
112
|
+
this.#doOpen();
|
|
113
|
+
} else if (this.#hasBeenOpen) {
|
|
114
|
+
this.#doClose();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
109
117
|
|
|
110
118
|
protected readonly panelClass = computed(() =>
|
|
111
119
|
cn(
|
|
@@ -192,9 +200,8 @@ export class UiNavDrawer implements OnDestroy {
|
|
|
192
200
|
}
|
|
193
201
|
|
|
194
202
|
#animateIn(): void {
|
|
195
|
-
const backdrop = this.backdropRef()
|
|
196
|
-
const panel = this.panelRef()
|
|
197
|
-
if (!backdrop || !panel) return;
|
|
203
|
+
const backdrop = this.backdropRef().nativeElement;
|
|
204
|
+
const panel = this.panelRef().nativeElement;
|
|
198
205
|
|
|
199
206
|
this.#backdropAnim?.cancel();
|
|
200
207
|
this.#panelAnim?.cancel();
|
|
@@ -213,9 +220,8 @@ export class UiNavDrawer implements OnDestroy {
|
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
#animateOut(): void {
|
|
216
|
-
const backdrop = this.backdropRef()
|
|
217
|
-
const panel = this.panelRef()
|
|
218
|
-
if (!backdrop || !panel) return;
|
|
223
|
+
const backdrop = this.backdropRef().nativeElement;
|
|
224
|
+
const panel = this.panelRef().nativeElement;
|
|
219
225
|
|
|
220
226
|
this.#backdropAnim?.cancel();
|
|
221
227
|
this.#panelAnim?.cancel();
|
|
@@ -258,6 +264,13 @@ export class UiNavDrawerHeader {
|
|
|
258
264
|
);
|
|
259
265
|
}
|
|
260
266
|
|
|
267
|
+
/**
|
|
268
|
+
* `UiNavDrawerItem`, for `contentChildren()`. A call rather than the class
|
|
269
|
+
* itself: for a class, Angular's test build keeps an arrow function that
|
|
270
|
+
* nothing calls, which an app's coverage report would show as uncovered.
|
|
271
|
+
*/
|
|
272
|
+
const navDrawerItem = () => UiNavDrawerItem;
|
|
273
|
+
|
|
261
274
|
@Component({
|
|
262
275
|
selector: 'ui-nav-drawer-content',
|
|
263
276
|
imports: [LYNX_ELEMENTS],
|
|
@@ -314,27 +327,29 @@ export class UiNavDrawerContent {
|
|
|
314
327
|
// Every nav item projected into this section (Home + each group item). Used to
|
|
315
328
|
// find the active one when the drawer opens. `descendants: true` reaches items
|
|
316
329
|
// nested inside the @for group wrappers, not just the direct content children.
|
|
317
|
-
readonly items = contentChildren(
|
|
330
|
+
readonly items = contentChildren(navDrawerItem(), { descendants: true });
|
|
318
331
|
|
|
319
332
|
protected readonly contentClass = computed(() =>
|
|
320
333
|
cn('h-full w-full', this.userClass()),
|
|
321
334
|
);
|
|
322
335
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
336
|
+
/**
|
|
337
|
+
* When the drawer opens, land the user on their current route instead of the
|
|
338
|
+
* top of a potentially long nav list.
|
|
339
|
+
*
|
|
340
|
+
* The <overlay> is display:none until open, so the scroll-view has no
|
|
341
|
+
* laid-out child frames until it's shown — defer a tick so the just-shown
|
|
342
|
+
* content is measured before we read it.
|
|
343
|
+
*
|
|
344
|
+
* This open-driven path is the fallback: it covers the web preview (where
|
|
345
|
+
* contentsizechanged isn't emitted) and re-opens that don't trigger a
|
|
346
|
+
* relayout. On device, onContentLayout() below is the precise trigger. Both
|
|
347
|
+
* call the same instant scroll, so firing twice is a harmless no-op.
|
|
348
|
+
*/
|
|
349
|
+
protected readonly revealActiveOnOpen = effect(() => {
|
|
350
|
+
if (!this.#drawer.open()) return;
|
|
351
|
+
setTimeout(() => this.#scrollActiveIntoView(), 0);
|
|
352
|
+
});
|
|
338
353
|
|
|
339
354
|
/**
|
|
340
355
|
* Fired by the scroll-view once its content finishes layout. Gated on the
|
|
@@ -347,30 +362,31 @@ export class UiNavDrawerContent {
|
|
|
347
362
|
|
|
348
363
|
#scrollActiveIntoView(): void {
|
|
349
364
|
this.items()
|
|
350
|
-
/* v8 ignore start -- .find predicate unreachable when no content-projected items */
|
|
351
365
|
.find((item) => item.active())
|
|
352
|
-
/* v8 ignore stop */
|
|
353
366
|
?.scrollIntoView();
|
|
354
367
|
}
|
|
355
368
|
}
|
|
356
369
|
|
|
370
|
+
/**
|
|
371
|
+
* The color icons inside an item draw with: the destructive color in a
|
|
372
|
+
* destructive item. A function of its own rather than written inline in
|
|
373
|
+
* `providers`: Angular's test build keeps a second copy of an inline factory
|
|
374
|
+
* that nothing calls, which an app's coverage report would show as uncovered.
|
|
375
|
+
*/
|
|
376
|
+
const itemIconColor = () => {
|
|
377
|
+
const item = inject(UiNavDrawerItem);
|
|
378
|
+
return computed(() =>
|
|
379
|
+
item.variant() === 'destructive' ? 'var(--destructive)' : undefined,
|
|
380
|
+
);
|
|
381
|
+
};
|
|
382
|
+
|
|
357
383
|
@Component({
|
|
358
384
|
selector: 'ui-nav-drawer-item',
|
|
359
385
|
imports: [LYNX_ELEMENTS],
|
|
360
386
|
encapsulation: ViewEncapsulation.None,
|
|
361
387
|
// Icons projected into the item (see the variant below) inject this through
|
|
362
388
|
// the item's element injector, which content projection keeps them under.
|
|
363
|
-
providers: [
|
|
364
|
-
{
|
|
365
|
-
provide: UI_ICON_COLOR,
|
|
366
|
-
useFactory: () => {
|
|
367
|
-
const item = inject(UiNavDrawerItem);
|
|
368
|
-
return computed(() =>
|
|
369
|
-
item.variant() === 'destructive' ? 'var(--destructive)' : undefined,
|
|
370
|
-
);
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
],
|
|
389
|
+
providers: [{ provide: UI_ICON_COLOR, useFactory: itemIconColor }],
|
|
374
390
|
template: `
|
|
375
391
|
<view
|
|
376
392
|
#container
|
|
@@ -1,110 +1,170 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
waitForUpdate,
|
|
6
|
+
within,
|
|
7
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
2
8
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { setInputSignal } from '../../../test-utils/set-input-signal';
|
|
4
9
|
import { ICON_LIGHT_COLORS } from '../icon/icon-theme';
|
|
5
10
|
import { UiPagination } from './pagination';
|
|
6
11
|
|
|
12
|
+
@Component({
|
|
13
|
+
imports: [UiPagination],
|
|
14
|
+
template: `
|
|
15
|
+
<ui-pagination
|
|
16
|
+
[(page)]="page"
|
|
17
|
+
[totalPages]="totalPages()"
|
|
18
|
+
[siblingCount]="siblingCount()"
|
|
19
|
+
class="mt-4"
|
|
20
|
+
/>
|
|
21
|
+
`,
|
|
22
|
+
})
|
|
23
|
+
class PaginationHost {
|
|
24
|
+
readonly page = signal(1);
|
|
25
|
+
readonly totalPages = signal(5);
|
|
26
|
+
readonly siblingCount = signal(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Component({
|
|
30
|
+
imports: [UiPagination],
|
|
31
|
+
template: `
|
|
32
|
+
<ui-pagination
|
|
33
|
+
[totalPages]="2"
|
|
34
|
+
previousLabel="Back"
|
|
35
|
+
nextLabel="Forward"
|
|
36
|
+
[pageLabel]="resultsLabel"
|
|
37
|
+
/>
|
|
38
|
+
`,
|
|
39
|
+
})
|
|
40
|
+
class LabeledPaginationHost {
|
|
41
|
+
readonly resultsLabel = (page: number, current: boolean): string =>
|
|
42
|
+
current ? `Results ${page}, showing` : `Results ${page}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
7
45
|
/**
|
|
8
|
-
*
|
|
46
|
+
* Renders the row at a page of a total, with `siblingCount` pages on each
|
|
47
|
+
* side of the current one.
|
|
9
48
|
*/
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
49
|
+
const renderPagination = async (
|
|
50
|
+
totalPages: number,
|
|
51
|
+
page = 1,
|
|
52
|
+
siblingCount = 1,
|
|
53
|
+
) => {
|
|
54
|
+
const result = await render(PaginationHost);
|
|
55
|
+
const host = result.componentRef.instance as PaginationHost;
|
|
56
|
+
host.totalPages.set(totalPages);
|
|
57
|
+
host.siblingCount.set(siblingCount);
|
|
58
|
+
host.page.set(page);
|
|
59
|
+
await waitForUpdate();
|
|
60
|
+
return {
|
|
61
|
+
...result,
|
|
62
|
+
host,
|
|
63
|
+
/** The button a screen reader reads as `label`. */
|
|
64
|
+
button: (label: string) => result.getByRole('button', { name: label }),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* What the row shows between its arrows, in order: page numbers and gaps.
|
|
70
|
+
*/
|
|
71
|
+
const shown = (container: Element): (string | null)[] =>
|
|
72
|
+
Array.from(container.querySelectorAll('text')).map(
|
|
73
|
+
(el) => el.textContent?.trim() ?? null,
|
|
13
74
|
);
|
|
14
75
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
76
|
+
/**
|
|
77
|
+
* The screen reader labels of the row's buttons, in order. getAllByRole()
|
|
78
|
+
* finds only what a screen reader reaches as a button, so this also checks
|
|
79
|
+
* each one's traits and accessibility-element.
|
|
80
|
+
*/
|
|
81
|
+
const labels = (container: Element): (string | null)[] =>
|
|
82
|
+
within(container as HTMLElement)
|
|
83
|
+
.getAllByRole('button')
|
|
84
|
+
.map((button) => button.getAttribute('accessibility-label'));
|
|
25
85
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
86
|
+
describe('UiPagination', () => {
|
|
87
|
+
it('lists every page when they all fit, with the given class', async () => {
|
|
88
|
+
// With one sibling, up to 5 + 1 * 2 = 7 pages fit without gaps.
|
|
89
|
+
const { container } = await renderPagination(7, 3);
|
|
90
|
+
expect(shown(container)).toEqual(['1', '2', '3', '4', '5', '6', '7']);
|
|
91
|
+
expect(container.querySelector('.gap-1')?.classList).toContain('mt-4');
|
|
29
92
|
});
|
|
30
93
|
|
|
31
|
-
it('
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Each page should render its numeric label.
|
|
35
|
-
const text = container.textContent ?? '';
|
|
36
|
-
expect(text).toContain('1');
|
|
37
|
-
expect(text).toContain('5');
|
|
38
|
-
expect(text).not.toContain('...');
|
|
94
|
+
it('leaves a gap before the last page near the start', async () => {
|
|
95
|
+
const { container } = await renderPagination(10, 2);
|
|
96
|
+
expect(shown(container)).toEqual(['1', '2', '3', '...', '10']);
|
|
39
97
|
});
|
|
40
98
|
|
|
41
|
-
it('
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
expect(container.textContent ?? '').toContain('...');
|
|
99
|
+
it('leaves a gap after the first page near the end', async () => {
|
|
100
|
+
const { container } = await renderPagination(10, 9);
|
|
101
|
+
expect(shown(container)).toEqual(['1', '...', '8', '9', '10']);
|
|
45
102
|
});
|
|
46
103
|
|
|
47
|
-
it('
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
expect(container.textContent ?? '').toContain('...');
|
|
104
|
+
it('leaves gaps on both sides in the middle', async () => {
|
|
105
|
+
const { container } = await renderPagination(10, 5);
|
|
106
|
+
expect(shown(container)).toEqual(['1', '...', '4', '5', '6', '...', '10']);
|
|
51
107
|
});
|
|
52
108
|
|
|
53
|
-
it('
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
109
|
+
it('widens the window around the current page with more siblings', async () => {
|
|
110
|
+
const { container } = await renderPagination(12, 6, 2);
|
|
111
|
+
expect(shown(container)).toEqual([
|
|
112
|
+
'1',
|
|
113
|
+
'...',
|
|
114
|
+
'4',
|
|
115
|
+
'5',
|
|
116
|
+
'6',
|
|
117
|
+
'7',
|
|
118
|
+
'8',
|
|
119
|
+
'...',
|
|
120
|
+
'12',
|
|
121
|
+
]);
|
|
60
122
|
});
|
|
61
123
|
|
|
62
|
-
it('
|
|
63
|
-
const {
|
|
64
|
-
(
|
|
65
|
-
|
|
66
|
-
expect(
|
|
124
|
+
it('highlights the current page', async () => {
|
|
125
|
+
const { button } = await renderPagination(3, 2);
|
|
126
|
+
expect(button('Page 2, current page').classList).toContain('bg-primary');
|
|
127
|
+
expect(button('Page 1').classList).toContain('bg-transparent');
|
|
128
|
+
expect(button('Page 2, current page').textContent?.trim()).toBe('2');
|
|
67
129
|
});
|
|
68
130
|
|
|
69
|
-
it('
|
|
70
|
-
const {
|
|
71
|
-
(
|
|
131
|
+
it('goes to a tapped page', async () => {
|
|
132
|
+
const { host, button } = await renderPagination(5, 1);
|
|
133
|
+
fireEvent.tap(button('Page 4'));
|
|
72
134
|
await waitForUpdate();
|
|
73
|
-
expect(
|
|
135
|
+
expect(host.page()).toBe(4);
|
|
136
|
+
expect(button('Page 4, current page').classList).toContain('bg-primary');
|
|
74
137
|
});
|
|
75
138
|
|
|
76
|
-
it('
|
|
77
|
-
const {
|
|
78
|
-
(
|
|
139
|
+
it('steps back and forward with the arrows', async () => {
|
|
140
|
+
const { host, button } = await renderPagination(5, 3);
|
|
141
|
+
fireEvent.tap(button('Previous page'));
|
|
79
142
|
await waitForUpdate();
|
|
80
|
-
expect(
|
|
81
|
-
});
|
|
143
|
+
expect(host.page()).toBe(2);
|
|
82
144
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
(inst as unknown as { next: () => void }).next();
|
|
145
|
+
fireEvent.tap(button('Next page'));
|
|
146
|
+
fireEvent.tap(button('Next page'));
|
|
86
147
|
await waitForUpdate();
|
|
87
|
-
expect(
|
|
148
|
+
expect(host.page()).toBe(4);
|
|
88
149
|
});
|
|
89
150
|
|
|
90
|
-
it(
|
|
91
|
-
const
|
|
92
|
-
(
|
|
151
|
+
it("dims the arrow that can't go further, and ignores its taps", async () => {
|
|
152
|
+
const first = await renderPagination(5, 1);
|
|
153
|
+
expect(first.button('Previous page').classList).toContain('opacity-50');
|
|
154
|
+
expect(first.button('Next page').classList).not.toContain('opacity-50');
|
|
155
|
+
fireEvent.tap(first.button('Previous page'));
|
|
93
156
|
await waitForUpdate();
|
|
94
|
-
expect(
|
|
95
|
-
});
|
|
157
|
+
expect(first.host.page()).toBe(1);
|
|
96
158
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
expect(primary).toBeTruthy();
|
|
159
|
+
const last = await renderPagination(5, 5);
|
|
160
|
+
expect(last.button('Next page').classList).toContain('opacity-50');
|
|
161
|
+
fireEvent.tap(last.button('Next page'));
|
|
162
|
+
await waitForUpdate();
|
|
163
|
+
expect(last.host.page()).toBe(5);
|
|
104
164
|
});
|
|
105
165
|
|
|
106
166
|
it('labels every button for screen readers', async () => {
|
|
107
|
-
const { container } = await
|
|
167
|
+
const { container } = await renderPagination(3, 2);
|
|
108
168
|
expect(labels(container)).toEqual([
|
|
109
169
|
'Previous page',
|
|
110
170
|
'Page 1',
|
|
@@ -112,20 +172,10 @@ describe('UiPagination', () => {
|
|
|
112
172
|
'Page 3',
|
|
113
173
|
'Next page',
|
|
114
174
|
]);
|
|
115
|
-
const buttons = container.querySelectorAll('[accessibility-element]');
|
|
116
|
-
for (const button of Array.from(buttons)) {
|
|
117
|
-
expect(button.getAttribute('accessibility-traits')).toBe('button');
|
|
118
|
-
}
|
|
119
175
|
});
|
|
120
176
|
|
|
121
177
|
it('takes its own labels', async () => {
|
|
122
|
-
const {
|
|
123
|
-
setInputSignal(inst.previousLabel, 'Back');
|
|
124
|
-
setInputSignal(inst.nextLabel, 'Forward');
|
|
125
|
-
setInputSignal(inst.pageLabel, (page: number, current: boolean) =>
|
|
126
|
-
current ? `Results ${page}, showing` : `Results ${page}`,
|
|
127
|
-
);
|
|
128
|
-
await waitForUpdate();
|
|
178
|
+
const { container } = await render(LabeledPaginationHost);
|
|
129
179
|
expect(labels(container)).toEqual([
|
|
130
180
|
'Back',
|
|
131
181
|
'Results 1, showing',
|
|
@@ -135,7 +185,7 @@ describe('UiPagination', () => {
|
|
|
135
185
|
});
|
|
136
186
|
|
|
137
187
|
it('hides the gaps from screen readers', async () => {
|
|
138
|
-
const { container } = await
|
|
188
|
+
const { container } = await renderPagination(10, 5);
|
|
139
189
|
const gaps = Array.from(container.querySelectorAll('text'))
|
|
140
190
|
.filter((el) => el.textContent === '...')
|
|
141
191
|
.map((el) =>
|
|
@@ -145,7 +195,7 @@ describe('UiPagination', () => {
|
|
|
145
195
|
});
|
|
146
196
|
|
|
147
197
|
it("draws the arrows in the theme's foreground", async () => {
|
|
148
|
-
const { container } = await
|
|
198
|
+
const { container } = await renderPagination(3, 2);
|
|
149
199
|
const arrows = Array.from(container.querySelectorAll('svg')).map((el) =>
|
|
150
200
|
el.getAttribute('content'),
|
|
151
201
|
);
|