@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 +12 -0
- package/declaration.d.ts +9 -0
- package/package.json +4 -1
- package/src/decorators/required-property.ts +5 -4
- package/src/decorators/test/required-property.spec.ts +94 -0
- package/src/decorators/test/valid-property-values.spec.ts +90 -0
- package/src/decorators/valid-property-values.ts +3 -3
- package/src/index.ts +1 -0
- package/src/test-helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts +23 -3
- package/src/type-helpers/DependentMap.ts +6 -0
- package/src/type-helpers/index.ts +1 -0
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
|
package/declaration.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justeattakeaway/pie-webc-core",
|
|
3
|
-
"version": "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:
|
|
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 ()
|
|
10
|
+
get (): T {
|
|
11
11
|
return this[privatePropertyKey];
|
|
12
12
|
},
|
|
13
|
-
set (value:
|
|
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:
|
|
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 ()
|
|
12
|
+
get (): T {
|
|
13
13
|
return this[privatePropertyKey];
|
|
14
14
|
},
|
|
15
|
-
set (value:
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import { LitElement, html, unsafeCSS } from 'lit';
|
|
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';
|