@operato/event-view 10.0.0 → 10.11.0

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/coverage/lcov-report/base.css +224 -0
  3. package/coverage/lcov-report/block-navigation.js +87 -0
  4. package/coverage/lcov-report/calendar-monthly-styles.ts.html +466 -0
  5. package/coverage/lcov-report/calendar-monthly.ts.html +769 -0
  6. package/coverage/lcov-report/calendar-weekly-styles.ts.html +508 -0
  7. package/coverage/lcov-report/calendar-weekly.ts.html +739 -0
  8. package/coverage/lcov-report/favicon.png +0 -0
  9. package/coverage/lcov-report/index.html +176 -0
  10. package/coverage/lcov-report/ox-event-view.ts.html +280 -0
  11. package/coverage/lcov-report/prettify.css +1 -0
  12. package/coverage/lcov-report/prettify.js +2 -0
  13. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  14. package/coverage/lcov-report/sorter.js +210 -0
  15. package/coverage/lcov-report/types.ts.html +199 -0
  16. package/coverage/lcov.info +6577 -0
  17. package/dist/src/calendar-monthly-styles.js +260 -16
  18. package/dist/src/calendar-monthly-styles.js.map +1 -1
  19. package/dist/src/calendar-monthly.d.ts +8 -1
  20. package/dist/src/calendar-monthly.js +90 -17
  21. package/dist/src/calendar-monthly.js.map +1 -1
  22. package/dist/src/calendar-weekly-styles.js +172 -21
  23. package/dist/src/calendar-weekly-styles.js.map +1 -1
  24. package/dist/src/calendar-weekly.d.ts +8 -1
  25. package/dist/src/calendar-weekly.js +92 -12
  26. package/dist/src/calendar-weekly.js.map +1 -1
  27. package/dist/src/day-overflow.d.ts +42 -0
  28. package/dist/src/day-overflow.js +71 -0
  29. package/dist/src/day-overflow.js.map +1 -0
  30. package/dist/src/index.d.ts +1 -0
  31. package/dist/src/index.js +1 -0
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/ox-event-view.d.ts +18 -1
  34. package/dist/src/ox-event-view.js +73 -15
  35. package/dist/src/ox-event-view.js.map +1 -1
  36. package/dist/src/types.d.ts +66 -1
  37. package/dist/src/types.js +64 -1
  38. package/dist/src/types.js.map +1 -1
  39. package/dist/test/calendar-layout.test.d.ts +1 -0
  40. package/dist/test/calendar-layout.test.js +188 -0
  41. package/dist/test/calendar-layout.test.js.map +1 -0
  42. package/dist/test/day-overflow.test.d.ts +1 -0
  43. package/dist/test/day-overflow.test.js +81 -0
  44. package/dist/test/day-overflow.test.js.map +1 -0
  45. package/dist/test/day-state.test.d.ts +1 -0
  46. package/dist/test/day-state.test.js +115 -0
  47. package/dist/test/day-state.test.js.map +1 -0
  48. package/dist/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +4 -2
  50. package/test/calendar-layout.test.ts +238 -0
  51. package/test/day-overflow.test.ts +103 -0
  52. package/test/day-state.test.ts +139 -0
@@ -0,0 +1,188 @@
1
+ import { html, fixture, expect, oneEvent } from '@open-wc/testing';
2
+ import '../src/ox-event-view.js';
3
+ /**
4
+ * Two things this calendar got wrong on a real screen, both of them structural.
5
+ *
6
+ * The package had no tests at all, which is why a layout that squeezed six of seven days to
7
+ * 58px and a view switch that told nobody both shipped.
8
+ */
9
+ /** One long unbreakable title on one day, the way a work order id looks. */
10
+ const LONG = 'WO-CT-MAIN-003 · CT-FRAME-SET-ASSEMBLY-STATION';
11
+ function providerWithLongTitleOn(index) {
12
+ return {
13
+ fetchEventsForCalendar: async (calendar) => {
14
+ const days = (calendar ?? []).filter(one => one?.date instanceof Date);
15
+ const map = new Map();
16
+ days.forEach((day, i) => {
17
+ map.set(day.date, [
18
+ {
19
+ title: i === index ? LONG : 'short',
20
+ color: 'currentColor',
21
+ /*
22
+ * nowrap is what a host page asks for when it wants one line per row and an
23
+ * ellipsis. It also makes the chip's min-content width the whole title, which is
24
+ * the pressure this test is about.
25
+ */
26
+ template: html `<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
27
+ ${i === index ? LONG : 'short'}
28
+ </div>`
29
+ }
30
+ ]);
31
+ });
32
+ return map;
33
+ }
34
+ };
35
+ }
36
+ async function settle(el) {
37
+ await el.updateComplete;
38
+ const inner = el.renderRoot.querySelector('calendar-monthly, calendar-weekly');
39
+ await inner?.updateComplete;
40
+ /* the provider is awaited inside updated(), so give it a turn and let layout settle */
41
+ await new Promise(resolve => setTimeout(resolve, 0));
42
+ await inner?.updateComplete;
43
+ return inner;
44
+ }
45
+ describe('the seven day columns stay the same width', () => {
46
+ for (const mode of ['monthly', 'weekly']) {
47
+ it(`${mode}: one long title does not take the room of the other six days`, async () => {
48
+ const el = (await fixture(html `<div style="width: 700px">
49
+ <ox-event-view mode=${mode} .eventProvider=${providerWithLongTitleOn(2)}></ox-event-view>
50
+ </div>`));
51
+ const view = el.querySelector('ox-event-view');
52
+ const inner = await settle(view);
53
+ const cells = [...inner.renderRoot.querySelectorAll('li[date]')];
54
+ expect(cells.length, 'no date cells were drawn, so this test measures nothing').to.be.greaterThan(6);
55
+ const widths = cells.slice(0, 7).map(one => Math.round(one.getBoundingClientRect().width));
56
+ const widest = Math.max(...widths);
57
+ const narrowest = Math.min(...widths);
58
+ /*
59
+ * Measured before min-width: 0 was added — [58, 58, 353, 58, 58, 58, 58] at this width.
60
+ * A pixel of rounding is fine; a column taking six times its share is not.
61
+ */
62
+ expect(widest - narrowest, `columns came out ${widths.join(', ')}`).to.be.lessThan(2);
63
+ });
64
+ }
65
+ });
66
+ describe('the built-in view switch tells the host', () => {
67
+ it('emits mode-change, because the host keeps its own state next to this one', async () => {
68
+ const el = (await fixture(html `<ox-event-view mode="monthly"></ox-event-view>`));
69
+ const inner = await settle(el);
70
+ const toggle = inner.querySelector('md-icon');
71
+ expect(toggle, 'the switch is slotted content — without it nothing can be clicked').to.exist;
72
+ const heard = oneEvent(el, 'mode-change');
73
+ toggle.click();
74
+ const event = await heard;
75
+ expect(event.detail.mode).to.equal('weekly');
76
+ expect(el.mode, 'the element still switches on its own, for hosts that ignore the event').to.equal('weekly');
77
+ });
78
+ it('reports monthly on the way back', async () => {
79
+ const el = (await fixture(html `<ox-event-view mode="weekly"></ox-event-view>`));
80
+ const inner = await settle(el);
81
+ const heard = oneEvent(el, 'mode-change');
82
+ inner.querySelector('md-icon').click();
83
+ expect((await heard).detail.mode).to.equal('monthly');
84
+ });
85
+ });
86
+ /**
87
+ * The calendar drew "Sun Mon Tue" over "September 2026" on a screen whose every other word was
88
+ * Korean: the weekday row was a literal English array and dayjs had no locale loaded.
89
+ *
90
+ * These read the rendered text rather than the helpers, because a helper that returns Korean
91
+ * proves nothing if the template still holds the old array.
92
+ */
93
+ describe('the calendar speaks the language the document declares', () => {
94
+ const original = document.documentElement.lang;
95
+ afterEach(() => {
96
+ document.documentElement.lang = original;
97
+ });
98
+ async function headerOf(lang, mode) {
99
+ document.documentElement.lang = lang;
100
+ const el = (await fixture(html `<ox-event-view mode=${mode}></ox-event-view>`));
101
+ const inner = await settle(el);
102
+ return inner.renderRoot;
103
+ }
104
+ for (const mode of ['monthly', 'weekly']) {
105
+ it(`${mode}: the weekday row is in Korean when the document says ko`, async () => {
106
+ const root = await headerOf('ko', mode);
107
+ const names = Array.from(root.querySelectorAll('li[day-of-week], [date-label]')).map(el => el.textContent.trim());
108
+ const row = names.join(' ');
109
+ expect(row, `weekday row came out "${row}"`).to.not.match(/Sun|Mon|Tue|Wed|Thu|Fri|Sat/);
110
+ expect(row, `weekday row came out "${row}"`).to.match(/[일월화수목금토]/);
111
+ });
112
+ it(`${mode}: the step buttons carry a name, not a "<" glyph`, async () => {
113
+ const root = await headerOf('ko', mode);
114
+ const buttons = Array.from(root.querySelectorAll('md-icon-button'));
115
+ expect(buttons.length, 'previous and next should both be icon buttons').to.equal(2);
116
+ for (const button of buttons) {
117
+ const name = button.getAttribute('aria-label') ?? '';
118
+ expect(name, 'an icon button with no accessible name is read as its glyph').to.not.equal('');
119
+ expect(name).to.not.match(/^[<>]$/);
120
+ }
121
+ });
122
+ }
123
+ it('monthly: the month title follows the language', async () => {
124
+ const root = await headerOf('ko', 'monthly');
125
+ const title = root.querySelector('[this-month]').textContent.trim();
126
+ expect(title, `title came out "${title}"`).to.not.match(/January|February|March|September|December/);
127
+ expect(title, `title came out "${title}"`).to.include('월');
128
+ });
129
+ it('monthly: today is named in the language, not the word "Today"', async () => {
130
+ const root = await headerOf('ko', 'monthly');
131
+ const labels = Array.from(root.querySelectorAll('md-text-button')).map(el => el.textContent.trim());
132
+ expect(labels.join(' '), `buttons came out "${labels.join(' ')}"`).to.not.include('Today');
133
+ expect(labels.some(l => l.includes('오늘')), `buttons came out "${labels.join(' ')}"`).to.be.true;
134
+ });
135
+ });
136
+ /**
137
+ * The day numbers were formatted once, when the grid was built, and the grid is rebuilt on a
138
+ * month change - not on a language change. So switching language left "Sep 6 (일)": the
139
+ * weekday came from the template and followed, the date came from the stored string and did
140
+ * not. Found on the running plant, with the weekday already in Korean beside it.
141
+ */
142
+ describe('the dates follow a language change, not just the weekday', () => {
143
+ const original = document.documentElement.lang;
144
+ afterEach(() => {
145
+ document.documentElement.lang = original;
146
+ });
147
+ for (const mode of ['monthly', 'weekly']) {
148
+ it(`${mode}: switching to ko rewrites the day labels, not only the weekday row`, async () => {
149
+ document.documentElement.lang = 'en';
150
+ const el = (await fixture(html `<ox-event-view mode=${mode}></ox-event-view>`));
151
+ const inner = await settle(el);
152
+ document.documentElement.lang = 'ko';
153
+ inner.requestUpdate();
154
+ await inner.updateComplete;
155
+ const labels = Array.from(inner.renderRoot.querySelectorAll('[date-label]'))
156
+ .map(el => el.textContent.trim())
157
+ .join(' ');
158
+ expect(labels, `day labels came out "${labels}"`).to.not.match(/Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/);
159
+ expect(labels, `day labels came out "${labels}"`).to.include('월');
160
+ });
161
+ }
162
+ });
163
+ /**
164
+ * The view switch was a bare md-icon: 24px, no padding, no hit area and no accessible name,
165
+ * sitting flush against a 40px button. On screen it read as four vertical bars.
166
+ */
167
+ describe('the view switch is a named button', () => {
168
+ for (const [mode, other] of [
169
+ ['monthly', 'week'],
170
+ ['weekly', 'month']
171
+ ]) {
172
+ it(`${mode}: the switch is an icon button that says where it goes`, async () => {
173
+ const el = (await fixture(html `<ox-event-view mode=${mode}></ox-event-view>`));
174
+ await settle(el);
175
+ const button = el.renderRoot.querySelector('md-icon-button');
176
+ expect(button, 'the switch should be a button, not a bare icon').to.exist;
177
+ const name = button.getAttribute('aria-label') ?? '';
178
+ expect(name, 'an icon with no name is read as its glyph').to.include(other);
179
+ });
180
+ }
181
+ it('a host can pass its own words', async () => {
182
+ const el = (await fixture(html `<ox-event-view mode="monthly" weekly-label="주간 보기"></ox-event-view>`));
183
+ await settle(el);
184
+ const button = el.renderRoot.querySelector('md-icon-button');
185
+ expect(button.getAttribute('aria-label')).to.equal('주간 보기');
186
+ });
187
+ });
188
+ //# sourceMappingURL=calendar-layout.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar-layout.test.js","sourceRoot":"","sources":["../../test/calendar-layout.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, fixture, expect, oneEvent } from '@open-wc/testing'\n\nimport '../src/ox-event-view.js'\nimport { OxEventView } from '../src/ox-event-view.js'\nimport { EventProvider, CALENDAR, BizEvent } from '../src/types.js'\n\n/**\n * Two things this calendar got wrong on a real screen, both of them structural.\n *\n * The package had no tests at all, which is why a layout that squeezed six of seven days to\n * 58px and a view switch that told nobody both shipped.\n */\n\n/** One long unbreakable title on one day, the way a work order id looks. */\nconst LONG = 'WO-CT-MAIN-003 · CT-FRAME-SET-ASSEMBLY-STATION'\n\nfunction providerWithLongTitleOn(index: number): EventProvider {\n return {\n fetchEventsForCalendar: async (calendar: CALENDAR) => {\n const days = (calendar ?? []).filter(one => one?.date instanceof Date)\n const map = new Map<Date, BizEvent[]>()\n\n days.forEach((day, i) => {\n map.set(day.date, [\n {\n title: i === index ? LONG : 'short',\n color: 'currentColor',\n /*\n * nowrap is what a host page asks for when it wants one line per row and an\n * ellipsis. It also makes the chip's min-content width the whole title, which is\n * the pressure this test is about.\n */\n template: html`<div style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis\">\n ${i === index ? LONG : 'short'}\n </div>`\n }\n ])\n })\n\n return map\n }\n }\n}\n\nasync function settle(el: OxEventView) {\n await el.updateComplete\n const inner = el.renderRoot.querySelector('calendar-monthly, calendar-weekly') as any\n await inner?.updateComplete\n /* the provider is awaited inside updated(), so give it a turn and let layout settle */\n await new Promise(resolve => setTimeout(resolve, 0))\n await inner?.updateComplete\n return inner\n}\n\ndescribe('the seven day columns stay the same width', () => {\n for (const mode of ['monthly', 'weekly'] as const) {\n it(`${mode}: one long title does not take the room of the other six days`, async () => {\n const el = (await fixture(\n html`<div style=\"width: 700px\">\n <ox-event-view mode=${mode} .eventProvider=${providerWithLongTitleOn(2)}></ox-event-view>\n </div>`\n )) as HTMLElement\n\n const view = el.querySelector('ox-event-view') as OxEventView\n const inner = await settle(view)\n\n const cells = [...inner.renderRoot.querySelectorAll('li[date]')] as HTMLElement[]\n expect(cells.length, 'no date cells were drawn, so this test measures nothing').to.be.greaterThan(6)\n\n const widths = cells.slice(0, 7).map(one => Math.round(one.getBoundingClientRect().width))\n const widest = Math.max(...widths)\n const narrowest = Math.min(...widths)\n\n /*\n * Measured before min-width: 0 was added — [58, 58, 353, 58, 58, 58, 58] at this width.\n * A pixel of rounding is fine; a column taking six times its share is not.\n */\n expect(widest - narrowest, `columns came out ${widths.join(', ')}`).to.be.lessThan(2)\n })\n }\n})\n\ndescribe('the built-in view switch tells the host', () => {\n it('emits mode-change, because the host keeps its own state next to this one', async () => {\n const el = (await fixture(html`<ox-event-view mode=\"monthly\"></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n\n const toggle = inner.querySelector('md-icon') as HTMLElement\n expect(toggle, 'the switch is slotted content — without it nothing can be clicked').to.exist\n\n const heard = oneEvent(el, 'mode-change')\n toggle.click()\n const event = await heard\n\n expect(event.detail.mode).to.equal('weekly')\n expect(el.mode, 'the element still switches on its own, for hosts that ignore the event').to.equal('weekly')\n })\n\n it('reports monthly on the way back', async () => {\n const el = (await fixture(html`<ox-event-view mode=\"weekly\"></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n\n const heard = oneEvent(el, 'mode-change')\n ;(inner.querySelector('md-icon') as HTMLElement).click()\n\n expect((await heard).detail.mode).to.equal('monthly')\n })\n})\n\n/**\n * The calendar drew \"Sun Mon Tue\" over \"September 2026\" on a screen whose every other word was\n * Korean: the weekday row was a literal English array and dayjs had no locale loaded.\n *\n * These read the rendered text rather than the helpers, because a helper that returns Korean\n * proves nothing if the template still holds the old array.\n */\ndescribe('the calendar speaks the language the document declares', () => {\n const original = document.documentElement.lang\n\n afterEach(() => {\n document.documentElement.lang = original\n })\n\n async function headerOf(lang: string, mode: 'monthly' | 'weekly') {\n document.documentElement.lang = lang\n const el = (await fixture(html`<ox-event-view mode=${mode}></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n return inner.renderRoot as ShadowRoot\n }\n\n for (const mode of ['monthly', 'weekly'] as const) {\n it(`${mode}: the weekday row is in Korean when the document says ko`, async () => {\n const root = await headerOf('ko', mode)\n const names = Array.from(root.querySelectorAll('li[day-of-week], [date-label]')).map(el =>\n el.textContent!.trim()\n )\n const row = names.join(' ')\n\n expect(row, `weekday row came out \"${row}\"`).to.not.match(/Sun|Mon|Tue|Wed|Thu|Fri|Sat/)\n expect(row, `weekday row came out \"${row}\"`).to.match(/[일월화수목금토]/)\n })\n\n it(`${mode}: the step buttons carry a name, not a \"<\" glyph`, async () => {\n const root = await headerOf('ko', mode)\n const buttons = Array.from(root.querySelectorAll('md-icon-button'))\n\n expect(buttons.length, 'previous and next should both be icon buttons').to.equal(2)\n for (const button of buttons) {\n const name = button.getAttribute('aria-label') ?? ''\n expect(name, 'an icon button with no accessible name is read as its glyph').to.not.equal('')\n expect(name).to.not.match(/^[<>]$/)\n }\n })\n }\n\n it('monthly: the month title follows the language', async () => {\n const root = await headerOf('ko', 'monthly')\n const title = root.querySelector('[this-month]')!.textContent!.trim()\n\n expect(title, `title came out \"${title}\"`).to.not.match(/January|February|March|September|December/)\n expect(title, `title came out \"${title}\"`).to.include('월')\n })\n\n it('monthly: today is named in the language, not the word \"Today\"', async () => {\n const root = await headerOf('ko', 'monthly')\n const labels = Array.from(root.querySelectorAll('md-text-button')).map(el => el.textContent!.trim())\n\n expect(labels.join(' '), `buttons came out \"${labels.join(' ')}\"`).to.not.include('Today')\n expect(labels.some(l => l.includes('오늘')), `buttons came out \"${labels.join(' ')}\"`).to.be.true\n })\n})\n\n/**\n * The day numbers were formatted once, when the grid was built, and the grid is rebuilt on a\n * month change - not on a language change. So switching language left \"Sep 6 (일)\": the\n * weekday came from the template and followed, the date came from the stored string and did\n * not. Found on the running plant, with the weekday already in Korean beside it.\n */\ndescribe('the dates follow a language change, not just the weekday', () => {\n const original = document.documentElement.lang\n\n afterEach(() => {\n document.documentElement.lang = original\n })\n\n for (const mode of ['monthly', 'weekly'] as const) {\n it(`${mode}: switching to ko rewrites the day labels, not only the weekday row`, async () => {\n document.documentElement.lang = 'en'\n const el = (await fixture(html`<ox-event-view mode=${mode}></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n\n document.documentElement.lang = 'ko'\n inner.requestUpdate()\n await inner.updateComplete\n\n const labels = Array.from((inner.renderRoot as ShadowRoot).querySelectorAll('[date-label]'))\n .map(el => el.textContent!.trim())\n .join(' ')\n\n expect(labels, `day labels came out \"${labels}\"`).to.not.match(\n /Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/\n )\n expect(labels, `day labels came out \"${labels}\"`).to.include('월')\n })\n }\n})\n\n/**\n * The view switch was a bare md-icon: 24px, no padding, no hit area and no accessible name,\n * sitting flush against a 40px button. On screen it read as four vertical bars.\n */\ndescribe('the view switch is a named button', () => {\n for (const [mode, other] of [\n ['monthly', 'week'],\n ['weekly', 'month']\n ] as const) {\n it(`${mode}: the switch is an icon button that says where it goes`, async () => {\n const el = (await fixture(html`<ox-event-view mode=${mode}></ox-event-view>`)) as OxEventView\n await settle(el)\n\n const button = (el.renderRoot as ShadowRoot).querySelector('md-icon-button')\n expect(button, 'the switch should be a button, not a bare icon').to.exist\n\n const name = button!.getAttribute('aria-label') ?? ''\n expect(name, 'an icon with no name is read as its glyph').to.include(other)\n })\n }\n\n it('a host can pass its own words', async () => {\n const el = (await fixture(\n html`<ox-event-view mode=\"monthly\" weekly-label=\"주간 보기\"></ox-event-view>`\n )) as OxEventView\n await settle(el)\n\n const button = (el.renderRoot as ShadowRoot).querySelector('md-icon-button')\n expect(button!.getAttribute('aria-label')).to.equal('주간 보기')\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ import '../src/ox-event-view.js';
@@ -0,0 +1,81 @@
1
+ import { html, fixture, expect } from '@open-wc/testing';
2
+ import '../src/ox-event-view.js';
3
+ /**
4
+ * A cell takes a ceiling so one busy day does not set the height of every day beside it, and
5
+ * says what the ceiling cut so the host can fold harder.
6
+ *
7
+ * The ceiling on its own would be the defect it replaces - the old "+763 more" hid rows the same
8
+ * way. What makes it different is that somebody is told.
9
+ */
10
+ const ROW_HEIGHT = 20;
11
+ function providerWith(rowsOnFirstDay) {
12
+ return {
13
+ async fetchEventsForCalendar(calendar) {
14
+ const events = Array.from({ length: rowsOnFirstDay }, (unused, index) => ({
15
+ title: `row ${index}`,
16
+ color: 'rgb(0, 0, 0)',
17
+ template: html `<div style="height: ${ROW_HEIGHT}px">row ${index}</div>`
18
+ }));
19
+ return new Map([[calendar[0].date, events]]);
20
+ }
21
+ };
22
+ }
23
+ async function calendarWith(rows, cap) {
24
+ const el = (await fixture(html `
25
+ <ox-event-view mode="monthly" style=${cap ? `--calendar-monthly-date-max-height:${cap}` : ''}></ox-event-view>
26
+ `));
27
+ const seen = [];
28
+ el.addEventListener('day-overflow', event => seen.push(event.detail));
29
+ el.eventProvider = providerWith(rows);
30
+ await el.updateComplete;
31
+ await new Promise(resolve => setTimeout(resolve, 80));
32
+ const root = el.renderRoot.querySelector('calendar-monthly').shadowRoot;
33
+ return { root, seen };
34
+ }
35
+ describe('a day cell that cannot hold everything says so', () => {
36
+ it('stays uncapped, and silent, when no theme sets a ceiling', async () => {
37
+ /*
38
+ * The default has to be none. A ceiling is only safe once the host folds its rows, so a
39
+ * component that capped by default would start hiding rows in every app that upgraded.
40
+ */
41
+ const { root, seen } = await calendarWith(40);
42
+ const cell = root.querySelector('li[date]');
43
+ expect(getComputedStyle(cell).maxHeight).to.equal('none');
44
+ expect(cell.getBoundingClientRect().height, 'the cell grew to hold all forty').to.be.greaterThan(40 * ROW_HEIGHT);
45
+ expect(seen, 'nothing was cut, so nothing is reported').to.deep.equal([]);
46
+ });
47
+ it('reports the rows a ceiling cut, and which day lost them', async () => {
48
+ const { root, seen } = await calendarWith(40, '160px');
49
+ const cell = root.querySelector('li[date]');
50
+ expect(cell.getBoundingClientRect().height, 'held to the ceiling').to.be.at.most(161);
51
+ const last = seen[seen.length - 1];
52
+ expect(last, 'the host was told').to.not.be.undefined;
53
+ expect(last.length).to.equal(1);
54
+ expect(last[0].hidden, 'roughly the rows past 160px, not all forty').to.be.greaterThan(20);
55
+ expect(last[0].date, 'named by day, so the host knows where to fold').to.be.instanceOf(Date);
56
+ });
57
+ it('keeps every other cell off the busy day\'s height', async () => {
58
+ /*
59
+ * The measured failure: a grid row is as tall as its tallest cell, so 767 events on the 8th
60
+ * made all seven of the week view's cells 670px, the two empty ones included.
61
+ */
62
+ const { root } = await calendarWith(40, '160px');
63
+ const cells = Array.from(root.querySelectorAll('li[date]'));
64
+ const heights = cells.map(cell => Math.round(cell.getBoundingClientRect().height));
65
+ expect(Math.max(...heights)).to.be.at.most(161);
66
+ });
67
+ it('does not repeat itself when nothing changed', async () => {
68
+ /*
69
+ * This measurement runs after every render, and a host that re-renders in response would
70
+ * otherwise be told the same thing forever.
71
+ */
72
+ const { seen } = await calendarWith(40, '160px');
73
+ const signatures = seen.map(one => one.map(day => `${day.date?.getTime()}:${day.hidden}`).join(','));
74
+ expect(new Set(signatures).size, 'one distinct report, however many renders').to.equal(signatures.length);
75
+ });
76
+ it('says nothing for a day that fits under the ceiling', async () => {
77
+ const { seen } = await calendarWith(2, '160px');
78
+ expect(seen.flat()).to.deep.equal([]);
79
+ });
80
+ });
81
+ //# sourceMappingURL=day-overflow.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"day-overflow.test.js","sourceRoot":"","sources":["../../test/day-overflow.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, fixture, expect } from '@open-wc/testing'\n\nimport '../src/ox-event-view.js'\nimport { OxEventView } from '../src/ox-event-view.js'\nimport { BizEvent, CALENDAR, EventProvider } from '../src/types.js'\nimport { DayOverflow } from '../src/day-overflow.js'\n\n/**\n * A cell takes a ceiling so one busy day does not set the height of every day beside it, and\n * says what the ceiling cut so the host can fold harder.\n *\n * The ceiling on its own would be the defect it replaces - the old \"+763 more\" hid rows the same\n * way. What makes it different is that somebody is told.\n */\n\nconst ROW_HEIGHT = 20\n\nfunction providerWith(rowsOnFirstDay: number): EventProvider {\n return {\n async fetchEventsForCalendar(calendar: CALENDAR) {\n const events: BizEvent[] = Array.from({ length: rowsOnFirstDay }, (unused, index) => ({\n title: `row ${index}`,\n color: 'rgb(0, 0, 0)',\n template: html`<div style=\"height: ${ROW_HEIGHT}px\">row ${index}</div>`\n }))\n\n return new Map([[calendar[0].date, events]])\n }\n }\n}\n\nasync function calendarWith(rows: number, cap?: string) {\n const el = (await fixture(html`\n <ox-event-view mode=\"monthly\" style=${cap ? `--calendar-monthly-date-max-height:${cap}` : ''}></ox-event-view>\n `)) as OxEventView\n\n const seen: DayOverflow[][] = []\n el.addEventListener('day-overflow', event => seen.push((event as CustomEvent<DayOverflow[]>).detail))\n\n el.eventProvider = providerWith(rows)\n await el.updateComplete\n await new Promise(resolve => setTimeout(resolve, 80))\n\n const root = el.renderRoot.querySelector('calendar-monthly')!.shadowRoot!\n return { root, seen }\n}\n\ndescribe('a day cell that cannot hold everything says so', () => {\n it('stays uncapped, and silent, when no theme sets a ceiling', async () => {\n /*\n * The default has to be none. A ceiling is only safe once the host folds its rows, so a\n * component that capped by default would start hiding rows in every app that upgraded.\n */\n const { root, seen } = await calendarWith(40)\n const cell = root.querySelector('li[date]') as HTMLElement\n\n expect(getComputedStyle(cell).maxHeight).to.equal('none')\n expect(cell.getBoundingClientRect().height, 'the cell grew to hold all forty').to.be.greaterThan(40 * ROW_HEIGHT)\n expect(seen, 'nothing was cut, so nothing is reported').to.deep.equal([])\n })\n\n it('reports the rows a ceiling cut, and which day lost them', async () => {\n const { root, seen } = await calendarWith(40, '160px')\n const cell = root.querySelector('li[date]') as HTMLElement\n\n expect(cell.getBoundingClientRect().height, 'held to the ceiling').to.be.at.most(161)\n\n const last = seen[seen.length - 1]\n expect(last, 'the host was told').to.not.be.undefined\n expect(last.length).to.equal(1)\n expect(last[0].hidden, 'roughly the rows past 160px, not all forty').to.be.greaterThan(20)\n expect(last[0].date, 'named by day, so the host knows where to fold').to.be.instanceOf(Date)\n })\n\n it('keeps every other cell off the busy day\\'s height', async () => {\n /*\n * The measured failure: a grid row is as tall as its tallest cell, so 767 events on the 8th\n * made all seven of the week view's cells 670px, the two empty ones included.\n */\n const { root } = await calendarWith(40, '160px')\n const cells = Array.from(root.querySelectorAll('li[date]')) as HTMLElement[]\n const heights = cells.map(cell => Math.round(cell.getBoundingClientRect().height))\n\n expect(Math.max(...heights)).to.be.at.most(161)\n })\n\n it('does not repeat itself when nothing changed', async () => {\n /*\n * This measurement runs after every render, and a host that re-renders in response would\n * otherwise be told the same thing forever.\n */\n const { seen } = await calendarWith(40, '160px')\n const signatures = seen.map(one => one.map(day => `${day.date?.getTime()}:${day.hidden}`).join(','))\n\n expect(new Set(signatures).size, 'one distinct report, however many renders').to.equal(signatures.length)\n })\n\n it('says nothing for a day that fits under the ceiling', async () => {\n const { seen } = await calendarWith(2, '160px')\n\n expect(seen.flat()).to.deep.equal([])\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ import '../src/ox-event-view.js';
@@ -0,0 +1,115 @@
1
+ import { html, fixture, expect } from '@open-wc/testing';
2
+ import '../src/ox-event-view.js';
3
+ /**
4
+ * A host can say something about a day itself, not only about what happened in it.
5
+ *
6
+ * Before this there was no such channel: CALENDAR carried only what the component works out
7
+ * (which month a date is in, whether it is today), so a screen wanting to mark a holiday had to
8
+ * send an event, and it drew as one more 6px dot among the day's actual work. That was the
9
+ * component's shape, not the screen's choice.
10
+ */
11
+ function providerMarking(pick, state) {
12
+ return {
13
+ async fetchEventsForCalendar() {
14
+ return new Map();
15
+ },
16
+ async fetchDayStatesForCalendar(calendar) {
17
+ /*
18
+ * Key by the very Date objects the component handed over. The component looks the day up
19
+ * by identity, the way it already does for events - a fresh Date with the same instant
20
+ * would not match, and a host that built its own would silently mark nothing.
21
+ */
22
+ return new Map(pick(calendar).map(date => [date, state]));
23
+ }
24
+ };
25
+ }
26
+ const HOLIDAY = { name: 'non-working', color: 'rgb(1, 2, 3)' };
27
+ /*
28
+ * A bare test page carries no theme, and every surface in this component is a var() onto one.
29
+ * Without these the month cells compute to transparent and "the state did not paint" looks the
30
+ * same as "nothing paints here at all" — which is precisely the confusion these tests exist to
31
+ * rule out. Only the tokens the cell surfaces read.
32
+ */
33
+ const THEME = {
34
+ '--md-sys-color-surface-container-lowest': 'rgb(255, 255, 255)',
35
+ '--md-sys-color-surface-variant': 'rgb(232, 235, 238)',
36
+ '--md-sys-color-surface': 'rgb(240, 242, 244)',
37
+ '--md-sys-color-tertiary-container': 'rgb(200, 220, 255)',
38
+ '--md-sys-color-primary': 'rgb(38, 46, 56)'
39
+ };
40
+ before(() => Object.entries(THEME).forEach(([name, value]) => document.documentElement.style.setProperty(name, value)));
41
+ after(() => Object.keys(THEME).forEach(name => document.documentElement.style.removeProperty(name)));
42
+ async function calendarWith(provider, mode) {
43
+ const el = (await fixture(html `<ox-event-view mode=${mode}></ox-event-view>`));
44
+ if (provider)
45
+ el.eventProvider = provider;
46
+ await el.updateComplete;
47
+ /* The grid is filled in updated(), after the provider has answered. */
48
+ await new Promise(resolve => setTimeout(resolve, 50));
49
+ const view = el.renderRoot.querySelector(mode === 'monthly' ? 'calendar-monthly' : 'calendar-weekly');
50
+ return view.shadowRoot;
51
+ }
52
+ describe('a host can mark the day itself', () => {
53
+ it('paints the marked day and leaves the rest alone', async () => {
54
+ const root = await calendarWith(providerMarking(calendar => [calendar[10].date], HOLIDAY), 'monthly');
55
+ const marked = root.querySelectorAll('li[day-state]');
56
+ expect(marked.length, 'exactly the day the host named').to.equal(1);
57
+ expect(getComputedStyle(marked[0]).backgroundColor).to.equal('rgb(1, 2, 3)');
58
+ expect(marked[0].getAttribute('day-state'), 'the host word reaches the dom').to.equal('non-working');
59
+ });
60
+ it('leaves every cell unmarked when the host does not answer', async () => {
61
+ /* The old EventProvider shape, which must keep working - the method is optional. */
62
+ const root = await calendarWith({ async fetchEventsForCalendar() { return new Map(); } }, 'monthly');
63
+ expect(root.querySelectorAll('li[day-state]').length).to.equal(0);
64
+ });
65
+ it('does not blank a cell when there is no state to draw', async () => {
66
+ /*
67
+ * The failure this guards. `background-color: var(--day-state-color)` with the property
68
+ * unset is invalid at computed-value time, which resolves to transparent - so an attribute
69
+ * written without a colour would erase the month surface underneath instead of adding to
70
+ * it. Unmarked days must keep the surface the month view gave them.
71
+ */
72
+ const root = await calendarWith(providerMarking(calendar => [calendar[10].date], HOLIDAY), 'monthly');
73
+ const plain = root.querySelector('li[current-month]:not([day-state])');
74
+ expect(getComputedStyle(plain).backgroundColor, 'the month surface, untouched').to.equal('rgb(255, 255, 255)');
75
+ });
76
+ it('marks both cells of a day in the week view', async () => {
77
+ /*
78
+ * A day is two cells stacked there - the date band and the events under it. Marking only
79
+ * the band would tint a 37px strip and leave the 600px body untouched.
80
+ */
81
+ const root = await calendarWith(providerMarking(calendar => [calendar[3].date], HOLIDAY), 'weekly');
82
+ const marked = Array.from(root.querySelectorAll('li[day-state]'));
83
+ expect(marked.length).to.equal(2);
84
+ expect(marked.some(li => li.hasAttribute('day-of-week')), 'the date band').to.be.true;
85
+ expect(marked.some(li => !li.hasAttribute('day-of-week')), 'the events under it').to.be.true;
86
+ marked.forEach(li => expect(getComputedStyle(li).backgroundColor).to.equal('rgb(1, 2, 3)'));
87
+ });
88
+ it('lets a selection win over the day state', async () => {
89
+ /*
90
+ * Both rules are (0,1,1), so the order they are written in is what decides. A day you have
91
+ * selected should look selected even if it is a holiday; a holiday you have not touched
92
+ * should look like a holiday. Get the order wrong and dragging across a holiday leaves a
93
+ * hole in the selection.
94
+ */
95
+ const root = await calendarWith(providerMarking(calendar => [calendar[10].date], HOLIDAY), 'monthly');
96
+ const cell = root.querySelector('li[day-state]');
97
+ cell.setAttribute('selected', '');
98
+ expect(getComputedStyle(cell).backgroundColor).to.not.equal('rgb(1, 2, 3)');
99
+ });
100
+ it('keeps today readable on a marked day', async () => {
101
+ /*
102
+ * Today rides on a background-image and an outline, precisely so it survives whatever claims
103
+ * the background colour. A holiday that is also today has to show both.
104
+ */
105
+ const root = await calendarWith(providerMarking(calendar => calendar.filter(one => one.today).map(one => one.date), HOLIDAY), 'monthly');
106
+ const today = root.querySelector('li[today]');
107
+ const style = getComputedStyle(today);
108
+ expect(today.hasAttribute('day-state'), 'today was the day marked').to.be.true;
109
+ expect(style.backgroundColor, 'the holiday surface').to.equal('rgb(1, 2, 3)');
110
+ expect(style.backgroundImage, 'today, still drawn on top').to.not.equal('none');
111
+ expect(style.outlineStyle, 'and the ring, which is an outline so it can sit inset').to.equal('solid');
112
+ expect(parseFloat(style.outlineWidth)).to.be.greaterThan(0);
113
+ });
114
+ });
115
+ //# sourceMappingURL=day-state.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"day-state.test.js","sourceRoot":"","sources":["../../test/day-state.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, fixture, expect } from '@open-wc/testing'\n\nimport '../src/ox-event-view.js'\nimport { OxEventView } from '../src/ox-event-view.js'\nimport { CALENDAR, DayState, EventProvider } from '../src/types.js'\n\n/**\n * A host can say something about a day itself, not only about what happened in it.\n *\n * Before this there was no such channel: CALENDAR carried only what the component works out\n * (which month a date is in, whether it is today), so a screen wanting to mark a holiday had to\n * send an event, and it drew as one more 6px dot among the day's actual work. That was the\n * component's shape, not the screen's choice.\n */\n\nfunction providerMarking(pick: (calendar: CALENDAR) => Date[], state: DayState): EventProvider {\n return {\n async fetchEventsForCalendar() {\n return new Map()\n },\n async fetchDayStatesForCalendar(calendar: CALENDAR) {\n /*\n * Key by the very Date objects the component handed over. The component looks the day up\n * by identity, the way it already does for events - a fresh Date with the same instant\n * would not match, and a host that built its own would silently mark nothing.\n */\n return new Map(pick(calendar).map(date => [date, state]))\n }\n }\n}\n\nconst HOLIDAY: DayState = { name: 'non-working', color: 'rgb(1, 2, 3)' }\n\n/*\n * A bare test page carries no theme, and every surface in this component is a var() onto one.\n * Without these the month cells compute to transparent and \"the state did not paint\" looks the\n * same as \"nothing paints here at all\" — which is precisely the confusion these tests exist to\n * rule out. Only the tokens the cell surfaces read.\n */\nconst THEME: Record<string, string> = {\n '--md-sys-color-surface-container-lowest': 'rgb(255, 255, 255)',\n '--md-sys-color-surface-variant': 'rgb(232, 235, 238)',\n '--md-sys-color-surface': 'rgb(240, 242, 244)',\n '--md-sys-color-tertiary-container': 'rgb(200, 220, 255)',\n '--md-sys-color-primary': 'rgb(38, 46, 56)'\n}\n\nbefore(() => Object.entries(THEME).forEach(([name, value]) => document.documentElement.style.setProperty(name, value)))\nafter(() => Object.keys(THEME).forEach(name => document.documentElement.style.removeProperty(name)))\n\nasync function calendarWith(provider: EventProvider | undefined, mode: 'monthly' | 'weekly') {\n const el = (await fixture(html`<ox-event-view mode=${mode}></ox-event-view>`)) as OxEventView\n if (provider) el.eventProvider = provider\n await el.updateComplete\n /* The grid is filled in updated(), after the provider has answered. */\n await new Promise(resolve => setTimeout(resolve, 50))\n\n const view = el.renderRoot.querySelector(mode === 'monthly' ? 'calendar-monthly' : 'calendar-weekly')!\n return view.shadowRoot!\n}\n\ndescribe('a host can mark the day itself', () => {\n it('paints the marked day and leaves the rest alone', async () => {\n const root = await calendarWith(providerMarking(calendar => [calendar[10].date], HOLIDAY), 'monthly')\n\n const marked = root.querySelectorAll('li[day-state]')\n expect(marked.length, 'exactly the day the host named').to.equal(1)\n expect(getComputedStyle(marked[0] as HTMLElement).backgroundColor).to.equal('rgb(1, 2, 3)')\n expect(marked[0].getAttribute('day-state'), 'the host word reaches the dom').to.equal('non-working')\n })\n\n it('leaves every cell unmarked when the host does not answer', async () => {\n /* The old EventProvider shape, which must keep working - the method is optional. */\n const root = await calendarWith({ async fetchEventsForCalendar() { return new Map() } }, 'monthly')\n\n expect(root.querySelectorAll('li[day-state]').length).to.equal(0)\n })\n\n it('does not blank a cell when there is no state to draw', async () => {\n /*\n * The failure this guards. `background-color: var(--day-state-color)` with the property\n * unset is invalid at computed-value time, which resolves to transparent - so an attribute\n * written without a colour would erase the month surface underneath instead of adding to\n * it. Unmarked days must keep the surface the month view gave them.\n */\n const root = await calendarWith(providerMarking(calendar => [calendar[10].date], HOLIDAY), 'monthly')\n\n const plain = root.querySelector('li[current-month]:not([day-state])') as HTMLElement\n expect(getComputedStyle(plain).backgroundColor, 'the month surface, untouched').to.equal('rgb(255, 255, 255)')\n })\n\n it('marks both cells of a day in the week view', async () => {\n /*\n * A day is two cells stacked there - the date band and the events under it. Marking only\n * the band would tint a 37px strip and leave the 600px body untouched.\n */\n const root = await calendarWith(providerMarking(calendar => [calendar[3].date], HOLIDAY), 'weekly')\n\n const marked = Array.from(root.querySelectorAll('li[day-state]')) as HTMLElement[]\n expect(marked.length).to.equal(2)\n expect(marked.some(li => li.hasAttribute('day-of-week')), 'the date band').to.be.true\n expect(marked.some(li => !li.hasAttribute('day-of-week')), 'the events under it').to.be.true\n marked.forEach(li => expect(getComputedStyle(li).backgroundColor).to.equal('rgb(1, 2, 3)'))\n })\n\n it('lets a selection win over the day state', async () => {\n /*\n * Both rules are (0,1,1), so the order they are written in is what decides. A day you have\n * selected should look selected even if it is a holiday; a holiday you have not touched\n * should look like a holiday. Get the order wrong and dragging across a holiday leaves a\n * hole in the selection.\n */\n const root = await calendarWith(providerMarking(calendar => [calendar[10].date], HOLIDAY), 'monthly')\n const cell = root.querySelector('li[day-state]') as HTMLElement\n\n cell.setAttribute('selected', '')\n\n expect(getComputedStyle(cell).backgroundColor).to.not.equal('rgb(1, 2, 3)')\n })\n\n it('keeps today readable on a marked day', async () => {\n /*\n * Today rides on a background-image and an outline, precisely so it survives whatever claims\n * the background colour. A holiday that is also today has to show both.\n */\n const root = await calendarWith(\n providerMarking(calendar => calendar.filter(one => one.today).map(one => one.date), HOLIDAY),\n 'monthly'\n )\n const today = root.querySelector('li[today]') as HTMLElement\n const style = getComputedStyle(today)\n\n expect(today.hasAttribute('day-state'), 'today was the day marked').to.be.true\n expect(style.backgroundColor, 'the holiday surface').to.equal('rgb(1, 2, 3)')\n expect(style.backgroundImage, 'today, still drawn on top').to.not.equal('none')\n expect(style.outlineStyle, 'and the ring, which is an outline so it can sit inset').to.equal('solid')\n expect(parseFloat(style.outlineWidth)).to.be.greaterThan(0)\n })\n})\n"]}