@justeattakeaway/pie-webc-core 0.3.0 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [Removed] - Function for CSS loading in Safari visual tests due to fix on Percy side. ([#575](https://github.com/justeattakeaway/pie/pull/575)) by [@JoshuaNg2332](https://github.com/JoshuaNg2332)
8
+
9
+ ## 0.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [Added] - New function to fix CSS loading in Safari Percy tests ([#534](https://github.com/justeattakeaway/pie/pull/534)) by [@siggerzz](https://github.com/siggerzz)
14
+
3
15
  ## 0.3.0
4
16
 
5
17
  ### Minor Changes
@@ -0,0 +1,9 @@
1
+ declare module '*.scss' {
2
+ const content: Record<string, string>;
3
+ export default content;
4
+ }
5
+
6
+ declare module '*.scss?inline' {
7
+ const content: Record<string, string>;
8
+ export default content;
9
+ }
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-webc-core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "PIE design system base classes, mixins and utilities for web components",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "author": "JustEatTakeaway - Design System Web Team",
8
8
  "license": "Apache-2.0",
9
+ "scripts": {
10
+ "test": "run -T vitest run"
11
+ },
9
12
  "volta": {
10
13
  "extends": "../../../package.json"
11
14
  }
@@ -3,17 +3,17 @@
3
3
  * If the property's value is `undefined`, `null` or empty string, an error is logged.
4
4
  * @returns {Function} - The decorator function.
5
5
  */
6
- export const requiredProperty = (componentName: string) => function (target: any, propertyKey: string) : void {
6
+ export const requiredProperty = <T>(componentName: string) => function validateRequiredProperty (target: object, propertyKey: string): void {
7
7
  const privatePropertyKey = `#${propertyKey}`;
8
8
 
9
9
  Object.defineProperty(target, propertyKey, {
10
- get () : any {
10
+ get (): T {
11
11
  return this[privatePropertyKey];
12
12
  },
13
- set (value: any) : void {
13
+ set (value: T): void {
14
14
  const oldValue = this[privatePropertyKey];
15
15
 
16
- if (value === undefined || value === null || value === '') {
16
+ if (value === undefined || value === null || (typeof value === 'string' && value.trim() === '')) {
17
17
  console.error(`<${componentName}> Missing required attribute "${propertyKey}"`);
18
18
  }
19
19
  this[privatePropertyKey] = value;
@@ -22,3 +22,4 @@ export const requiredProperty = (componentName: string) => function (target: any
22
22
  },
23
23
  });
24
24
  };
25
+
@@ -0,0 +1,94 @@
1
+ import {
2
+ beforeEach,
3
+ afterEach,
4
+ describe,
5
+ it,
6
+ expect,
7
+ vi,
8
+ } from 'vitest';
9
+
10
+ import { requiredProperty } from '../required-property';
11
+
12
+ describe('requiredProperty', () => {
13
+ let consoleErrorSpy: unknown;
14
+
15
+ beforeEach(() => {
16
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
17
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
18
+ });
19
+
20
+ afterEach(() => {
21
+ vi.restoreAllMocks();
22
+ });
23
+
24
+ // Mock class to test the decorator
25
+ class MockComponent {
26
+ @requiredProperty('mock-component')
27
+ color?: string | null;
28
+
29
+ private _requestUpdateArgs = {};
30
+
31
+ requestUpdate (propertyKey: string, oldValue: unknown) {
32
+ this._requestUpdateArgs = { propertyKey, oldValue };
33
+ }
34
+
35
+ requestUpdateCalledWith () {
36
+ return this._requestUpdateArgs;
37
+ }
38
+ }
39
+
40
+ it('should log an error if the property is undefined', () => {
41
+ // Arrange
42
+ const mockComponent = new MockComponent();
43
+
44
+ // Act
45
+ mockComponent.color = undefined;
46
+
47
+ // Assert
48
+ expect(consoleErrorSpy).toHaveBeenCalled();
49
+ });
50
+
51
+ it('should log an error if the property is null', () => {
52
+ // Arrange
53
+ const mockComponent = new MockComponent();
54
+
55
+ // Act
56
+ mockComponent.color = null;
57
+
58
+ // Assert
59
+ expect(consoleErrorSpy).toHaveBeenCalled();
60
+ });
61
+
62
+ it('should log an error if the property is an empty string', () => {
63
+ // Arrange
64
+ const mockComponent = new MockComponent();
65
+
66
+ // Act
67
+ mockComponent.color = '';
68
+
69
+ // Assert
70
+ expect(consoleErrorSpy).toHaveBeenCalled();
71
+ });
72
+
73
+ it('should not log an error if the property is a non-empty string', () => {
74
+ // Arrange
75
+ const mockComponent = new MockComponent();
76
+
77
+ // Act
78
+ mockComponent.color = 'blue';
79
+
80
+ // Assert
81
+ expect(consoleErrorSpy).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it('should call requestUpdate when the property is set', () => {
85
+ // Arrange
86
+ const mockComponent = new MockComponent();
87
+
88
+ // Act
89
+ mockComponent.color = 'blue';
90
+
91
+ // Assert
92
+ expect(mockComponent.requestUpdateCalledWith()).toStrictEqual({ propertyKey: 'color', oldValue: undefined });
93
+ });
94
+ });
@@ -0,0 +1,90 @@
1
+ import {
2
+ beforeEach,
3
+ afterEach,
4
+ describe,
5
+ it,
6
+ expect,
7
+ vi,
8
+ } from 'vitest';
9
+
10
+ import { validPropertyValues } from '../valid-property-values';
11
+
12
+ describe('validPropertyValues', () => {
13
+ let consoleErrorSpy: unknown;
14
+
15
+ beforeEach(() => {
16
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
17
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
18
+ });
19
+
20
+ afterEach(() => {
21
+ vi.restoreAllMocks();
22
+ });
23
+
24
+ // Mock class to test the decorator
25
+ class MockComponent {
26
+ @validPropertyValues('mock-component', ['red', 'green', 'blue'], 'red')
27
+ color = 'red';
28
+
29
+ private _requestUpdateArgs = {};
30
+
31
+ requestUpdate (propertyKey: string, oldValue: unknown) {
32
+ this._requestUpdateArgs = { propertyKey, oldValue };
33
+ }
34
+
35
+ requestUpdateCalledWith () {
36
+ return this._requestUpdateArgs;
37
+ }
38
+ }
39
+
40
+ it('should allow value to be updated with a valid value', () => {
41
+ // Arrange
42
+ const mockComponent = new MockComponent();
43
+
44
+ // Act
45
+ mockComponent.color = 'green';
46
+
47
+ // Assert
48
+ expect(mockComponent.color).toBe('green');
49
+ expect(consoleErrorSpy).not.toHaveBeenCalled();
50
+ });
51
+
52
+ it('should fallback to the default value if an invalid value is assigned', () => {
53
+ // Arrange
54
+ const mockComponent = new MockComponent();
55
+
56
+ // Act
57
+ mockComponent.color = 'yellow';
58
+
59
+ // Assert
60
+ expect(mockComponent.color).toBe('red');
61
+ expect(consoleErrorSpy).toHaveBeenCalled();
62
+ });
63
+
64
+ it('should log an error message if an invalid value is assigned', () => {
65
+ // Arrange
66
+ const mockComponent = new MockComponent();
67
+
68
+ // Act
69
+ mockComponent.color = 'yellow';
70
+
71
+ // Assert
72
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
73
+ '<mock-component> Invalid value "yellow" provided for property "color".',
74
+ 'Must be one of: red | green | blue.',
75
+ 'Falling back to default value: "red"',
76
+ );
77
+ });
78
+
79
+ it('should call requestUpdate when the property is set', () => {
80
+ // Arrange
81
+ const mockComponent = new MockComponent();
82
+
83
+ // Act
84
+ mockComponent.color = 'yellow';
85
+
86
+ // Assert
87
+ expect(mockComponent.color).toBe('red');
88
+ expect(mockComponent.requestUpdateCalledWith()).toStrictEqual({ propertyKey: 'color', oldValue: 'red' });
89
+ });
90
+ });
@@ -5,14 +5,14 @@
5
5
  * @param defaultValue - The value to fall back on
6
6
  * @returns - The decorator function
7
7
  */
8
- export const validPropertyValues = (componentName: string, validValues: any[], defaultValue: any) => function (target: any, propertyKey: string) : void {
8
+ export const validPropertyValues = <T>(componentName: string, validValues: readonly T[], defaultValue: T) => function validatePropertyValues (target: object, propertyKey: string): void {
9
9
  const privatePropertyKey = `#${propertyKey}`;
10
10
 
11
11
  Object.defineProperty(target, propertyKey, {
12
- get () : any {
12
+ get (): T {
13
13
  return this[privatePropertyKey];
14
14
  },
15
- set (value: any) : void {
15
+ set (value: T): void {
16
16
  const oldValue = this[privatePropertyKey];
17
17
 
18
18
  if (!validValues.includes(value)) {
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './mixins';
2
2
  export * from './decorators';
3
3
  export * from './test-helpers';
4
+ export * from './type-helpers';
@@ -1,4 +1,4 @@
1
- import { LitElement, html, unsafeCSS } from 'lit'; // eslint-disable-line import/no-extraneous-dependencies
1
+ import { LitElement, html, unsafeCSS } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import styles from './webComponentTestWrapper.scss?inline';
4
4
 
@@ -7,6 +7,12 @@ import styles from './webComponentTestWrapper.scss?inline';
7
7
  * It allows us to wrap a component we'd like to test in a container
8
8
  * that displays the component's props as a label.
9
9
  *
10
+ * Alternatively, this component supports a page mode. In this mode,
11
+ * the test component is not wrapped and labelled, however you can add
12
+ * any amount of HTML to the page to help with testing. This useful for components
13
+ * such as Modal and Dialog components where we need to test the component in the context of
14
+ * the page markup.
15
+ *
10
16
  * Components can be tested without this, but it's useful if your tests
11
17
  * require additional markup when testing a component.
12
18
  */
@@ -21,6 +27,13 @@ export class WebComponentTestWrapper extends LitElement {
21
27
  @property({ type: String })
22
28
  propKeyValues = '';
23
29
 
30
+ /**
31
+ * Use in combination with the `pageMarkup` named slot to render the component alongside additional HTML
32
+ * to test the component in the context of the page.
33
+ */
34
+ @property({ type: Boolean })
35
+ pageMode = false;
36
+
24
37
  // Renders a string such as 'size: small, isFullWidth: true'
25
38
  // as HTML such as:
26
39
  // <p class="c-webComponentTestWrapper-label"><b>size</b>: <code>small</code></p>
@@ -33,13 +46,20 @@ export class WebComponentTestWrapper extends LitElement {
33
46
  });
34
47
  }
35
48
 
36
- // eslint-disable-next-line class-methods-use-this
37
49
  render () {
50
+ if (this.pageMode) {
51
+ return html`
52
+ <div>
53
+ <slot name="component"></slot>
54
+ <slot name="pageMarkup"></slot>
55
+ </div>`;
56
+ }
57
+
38
58
  return html`
39
59
  <div class="c-webComponentTestWrapper">
40
60
  ${this._renderPropKeyValues()}
41
61
  <div class="c-webComponentTestWrapper-slot">
42
- <slot></slot>
62
+ <slot name="component"></slot>
43
63
  </div>
44
64
  </div>`;
45
65
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A type alias that represents a specialized version of the built-in Map type, but with a custom get method that enforces stricter type checking
3
+ */
4
+ export type DependentMap<T> = Omit<Map<keyof T, T[keyof T]>, 'get'> & {
5
+ get<K extends keyof T>(key: K): T[K] | undefined;
6
+ }
@@ -0,0 +1 @@
1
+ export * from './DependentMap';