@astryxdesign/lab 0.7.1-canary.e199fa4 → 0.7.1-canary.e71c4e9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/lab",
3
- "version": "0.7.1-canary.e199fa4",
3
+ "version": "0.7.1-canary.e71c4e9",
4
4
  "description": "Experimental Astryx components — published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
5
5
  "author": "Meta Open Source",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@stylexjs/stylex": ">=0.10.0",
50
- "@astryxdesign/core": "0.7.1-canary.e199fa4",
50
+ "@astryxdesign/core": "0.7.1-canary.e71c4e9",
51
51
  "react": ">=19.0.0",
52
52
  "react-dom": ">=19.0.0"
53
53
  },
@@ -57,8 +57,8 @@
57
57
  "d3-shape": "^3.2.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@astryxdesign/a11y-spec": "0.7.1-canary.e199fa4",
61
- "@astryxdesign/cli": "0.7.1-canary.e199fa4",
60
+ "@astryxdesign/a11y-spec": "0.7.1-canary.e71c4e9",
61
+ "@astryxdesign/cli": "0.7.1-canary.e71c4e9",
62
62
  "@babel/cli": "^7.29.7",
63
63
  "@babel/core": "^7.29.7",
64
64
  "@babel/preset-react": "^7.29.7",
@@ -634,6 +634,103 @@ describe('Schedule', () => {
634
634
  });
635
635
  });
636
636
 
637
+ describe('Schedule across a midnight that daylight saving skips', () => {
638
+ // Both zones spring forward at local midnight: the day starts at 01:00.
639
+ const cases = [
640
+ {
641
+ timezoneID: 'America/Santiago',
642
+ date: Date.UTC(2026, 8, 8, 15),
643
+ week: [
644
+ 'Sunday, September 6, 2026',
645
+ 'Monday, September 7, 2026',
646
+ 'Tuesday, September 8, 2026',
647
+ 'Wednesday, September 9, 2026',
648
+ 'Thursday, September 10, 2026',
649
+ 'Friday, September 11, 2026',
650
+ 'Saturday, September 12, 2026',
651
+ ],
652
+ },
653
+ {
654
+ timezoneID: 'America/Havana',
655
+ date: Date.UTC(2026, 2, 10, 15),
656
+ week: [
657
+ 'Sunday, March 8, 2026',
658
+ 'Monday, March 9, 2026',
659
+ 'Tuesday, March 10, 2026',
660
+ 'Wednesday, March 11, 2026',
661
+ 'Thursday, March 12, 2026',
662
+ 'Friday, March 13, 2026',
663
+ 'Saturday, March 14, 2026',
664
+ ],
665
+ },
666
+ ];
667
+ const renderPopover = () => null;
668
+ const dayGroups = () =>
669
+ screen
670
+ .getAllByRole('group')
671
+ .map(group => group.getAttribute('aria-label') ?? '')
672
+ .filter(name => /, \d{4}$/u.test(name));
673
+
674
+ it.each(cases)(
675
+ 'keeps seven days in the week and one in the day view in $timezoneID',
676
+ ({timezoneID, date, week}) => {
677
+ const {unmount} = render(
678
+ <Schedule
679
+ view={createScheduleWeeklyView({renderPopover})}
680
+ events={[]}
681
+ date={date}
682
+ timezoneID={timezoneID}
683
+ />,
684
+ );
685
+ expect(dayGroups()).toEqual(week);
686
+ unmount();
687
+ render(
688
+ <Schedule
689
+ view={createScheduleDayView({renderPopover})}
690
+ events={[]}
691
+ date={date - 2 * 24 * 60 * 60 * 1000}
692
+ timezoneID={timezoneID}
693
+ />,
694
+ );
695
+ expect(dayGroups()).toEqual([week[0]]);
696
+ },
697
+ );
698
+
699
+ it('keeps seven days in the week and one in the day view that end at the skipped midnight', () => {
700
+ // Santiago's week of Sunday, August 30, 2026 ends on Saturday, September 5;
701
+ // its end is the start of September 6, a midnight that never happens.
702
+ const timezoneID = 'America/Santiago';
703
+ const date = Date.UTC(2026, 8, 5, 15);
704
+ const {unmount} = render(
705
+ <Schedule
706
+ view={createScheduleWeeklyView({renderPopover})}
707
+ events={[]}
708
+ date={date}
709
+ timezoneID={timezoneID}
710
+ />,
711
+ );
712
+ expect(dayGroups()).toEqual([
713
+ 'Sunday, August 30, 2026',
714
+ 'Monday, August 31, 2026',
715
+ 'Tuesday, September 1, 2026',
716
+ 'Wednesday, September 2, 2026',
717
+ 'Thursday, September 3, 2026',
718
+ 'Friday, September 4, 2026',
719
+ 'Saturday, September 5, 2026',
720
+ ]);
721
+ unmount();
722
+ render(
723
+ <Schedule
724
+ view={createScheduleDayView({renderPopover})}
725
+ events={[]}
726
+ date={date}
727
+ timezoneID={timezoneID}
728
+ />,
729
+ );
730
+ expect(dayGroups()).toEqual(['Saturday, September 5, 2026']);
731
+ });
732
+ });
733
+
637
734
  describe('sortEvents', () => {
638
735
  it('sorts mixed all-day and instant events by start time', () => {
639
736
  const sortedEvents = sortEvents(
@@ -0,0 +1,113 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file ScheduleTimeGridSkippedMidnight.a11y.chromium.spec.ts
5
+ * @input The built Lab/Schedule SkippedMidnight story in real Chromium
6
+ * @output Receipted evidence that a week and a day whose midnight daylight
7
+ * saving skips keep their own dates: seven day columns in the week, one in
8
+ * the day view, in America/Santiago and America/Havana
9
+ * @position Browser binding for `component:Schedule` FR8 (no event or day is
10
+ * dropped) across a skipped midnight
11
+ */
12
+
13
+ import {expect, test, type Page} from '@playwright/test';
14
+ import {
15
+ finishEvidence,
16
+ record,
17
+ startEvidence,
18
+ storyUrl,
19
+ WIDE,
20
+ type Evidence,
21
+ } from './timeGridProbe';
22
+
23
+ const STORY = 'lab-schedule--skipped-midnight';
24
+ const ZONES = [
25
+ {
26
+ timezoneID: 'America/Santiago',
27
+ week: [
28
+ 'Sunday, September 6, 2026',
29
+ 'Monday, September 7, 2026',
30
+ 'Tuesday, September 8, 2026',
31
+ 'Wednesday, September 9, 2026',
32
+ 'Thursday, September 10, 2026',
33
+ 'Friday, September 11, 2026',
34
+ 'Saturday, September 12, 2026',
35
+ ],
36
+ },
37
+ {
38
+ timezoneID: 'America/Havana',
39
+ week: [
40
+ 'Sunday, March 8, 2026',
41
+ 'Monday, March 9, 2026',
42
+ 'Tuesday, March 10, 2026',
43
+ 'Wednesday, March 11, 2026',
44
+ 'Thursday, March 12, 2026',
45
+ 'Friday, March 13, 2026',
46
+ 'Saturday, March 14, 2026',
47
+ ],
48
+ },
49
+ ] as const;
50
+
51
+ let evidence: Evidence;
52
+
53
+ /** The story holds four schedules, so wait for each zone's pair. */
54
+ async function openSkippedMidnight(page: Page, globals?: string) {
55
+ await page.setViewportSize(WIDE);
56
+ await page.goto(storyUrl(evidence, STORY, globals), {waitUntil: 'load'});
57
+ await expect(page.locator('.astryx-schedule')).toHaveCount(4);
58
+ await page.evaluate(() => document.fonts.ready);
59
+ await page.waitForTimeout(100);
60
+ }
61
+ test.beforeAll(async () => {
62
+ evidence = await startEvidence();
63
+ });
64
+ test.afterAll(async () => {
65
+ await finishEvidence(evidence, 'skipped-midnight');
66
+ });
67
+
68
+ for (const [direction, globals] of [
69
+ ['ltr', undefined],
70
+ ['rtl', 'direction:rtl'],
71
+ ] as const) {
72
+ test(`a skipped midnight keeps seven days in the week and one in the day view (${direction})`, async ({
73
+ page,
74
+ }) => {
75
+ await openSkippedMidnight(page, globals);
76
+ const readings = [];
77
+ for (const zone of ZONES) {
78
+ const section = page.getByRole('region', {name: zone.timezoneID});
79
+ const grids = section.getByRole('grid', {name: 'Schedule time grid'});
80
+ await expect(grids).toHaveCount(2);
81
+ const days = async (index: number) =>
82
+ (
83
+ await grids
84
+ .nth(index)
85
+ .getByRole('columnheader')
86
+ .evaluateAll(headers =>
87
+ headers.map(header => header.textContent?.trim() ?? ''),
88
+ )
89
+ ).filter(name => /, \d{4}$/u.test(name));
90
+ const week = await days(0);
91
+ const day = await days(1);
92
+ // The first hour's event paints in the first day's column.
93
+ const firstHour = await section
94
+ .getByText('First hour', {exact: true})
95
+ .first()
96
+ .isVisible();
97
+ readings.push({zone: zone.timezoneID, week, day, firstHour});
98
+ expect(week, `${zone.timezoneID} week`).toEqual([...zone.week]);
99
+ expect(day, `${zone.timezoneID} day`).toEqual([zone.week[0]]);
100
+ expect(firstHour, `${zone.timezoneID} first hour painted`).toBe(true);
101
+ }
102
+ await record(
103
+ evidence,
104
+ page,
105
+ `skipped-midnight-${direction}`,
106
+ STORY,
107
+ direction,
108
+ {
109
+ readings,
110
+ },
111
+ );
112
+ });
113
+ }