@oslokommune/punkt-elements 13.5.3 → 13.5.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/CHANGELOG.md +17 -0
- package/dist/{loader-CHPxY9c6.cjs → loader-DNidjwH-.cjs} +6 -1
- package/dist/{loader-Da4IOk_T.js → loader-h3d-3D7s.js} +6 -1
- package/dist/{messagebox-DwGdcdm7.js → messagebox-C8KQgCl_.js} +14 -13
- package/dist/{messagebox-CqUBJs_D.cjs → messagebox-CjPtPPrW.cjs} +1 -0
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +2 -2
- package/dist/pkt-loader.cjs +1 -1
- package/dist/pkt-loader.js +1 -1
- package/dist/pkt-messagebox.cjs +1 -1
- package/dist/pkt-messagebox.js +1 -1
- package/package.json +2 -2
- package/src/components/listbox/listbox.test.ts +225 -0
- package/src/components/loader/loader.test.ts +257 -0
- package/src/components/loader/loader.ts +6 -1
- package/src/components/messagebox/messagebox.test.ts +241 -0
- package/src/components/messagebox/messagebox.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ og skriver commits ca etter [Conventional Commits](https://conventionalcommits.o
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [13.5.4](https://github.com/oslokommune/punkt/compare/13.5.3...13.5.4) (2025-09-09)
|
|
9
|
+
|
|
10
|
+
### ⚠ BREAKING CHANGES
|
|
11
|
+
Ingen
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
Ingen
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
Ingen
|
|
18
|
+
|
|
19
|
+
### Chores
|
|
20
|
+
Ingen
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
|
|
8
25
|
## [13.5.3](https://github.com/oslokommune/punkt/compare/13.5.2...13.5.3) (2025-09-08)
|
|
9
26
|
|
|
10
27
|
### ⚠ BREAKING CHANGES
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
"use strict";const t=require("./element-6DBpyGQm.cjs"),d=require("./class-map-BBG2gMX4.cjs"),h=require("./state-DPobt-Yz.cjs"),p=require("./ref-iJtiv3o2.cjs"),u=require("./pkt-slot-controller-BzddBp7z.cjs");require("./icon-B_ryAy4Q.cjs");var c=Object.defineProperty,y=Object.getOwnPropertyDescriptor,s=(r,e,a,o)=>{for(var i=o>1?void 0:o?y(e,a):e,n=r.length-1,l;n>=0;n--)(l=r[n])&&(i=(o?l(e,a,i):l(i))||i);return o&&i&&c(e,a,i),i};window.pktAnimationPath=window.pktAnimationPath||"https://punkt-cdn.oslo.kommune.no/latest/animations/";exports.PktLoader=class extends t.PktElement{constructor(){super(),this.defaultSlot=p.e(),this.delay=0,this.inline=!1,this.isLoading=!0,this.message=null,this.size="medium",this.variant="shapes",this.loadingAnimationPath=window.pktAnimationPath,this._shouldDisplayLoader=!1,this.slotController=new u.PktSlotController(this,this.defaultSlot)}connectedCallback(){super.connectedCallback(),this._shouldDisplayLoader=this.delay===0,this.delay>0&&this.setupLoader()}updated(e){e.has("delay")&&this.setupLoader()}render(){const e=d.e({"pkt-loader":!0,[`pkt-loader--${this.inline?"inline":"box"}`]:!0,[`pkt-loader--${this.size}`]:!0}),a=d.e({"pkt-contents":!0,"pkt-hide":this.isLoading});return t.x`<div
|
|
1
|
+
"use strict";const t=require("./element-6DBpyGQm.cjs"),d=require("./class-map-BBG2gMX4.cjs"),h=require("./state-DPobt-Yz.cjs"),p=require("./ref-iJtiv3o2.cjs"),u=require("./pkt-slot-controller-BzddBp7z.cjs");require("./icon-B_ryAy4Q.cjs");var c=Object.defineProperty,y=Object.getOwnPropertyDescriptor,s=(r,e,a,o)=>{for(var i=o>1?void 0:o?y(e,a):e,n=r.length-1,l;n>=0;n--)(l=r[n])&&(i=(o?l(e,a,i):l(i))||i);return o&&i&&c(e,a,i),i};window.pktAnimationPath=window.pktAnimationPath||"https://punkt-cdn.oslo.kommune.no/latest/animations/";exports.PktLoader=class extends t.PktElement{constructor(){super(),this.defaultSlot=p.e(),this.delay=0,this.inline=!1,this.isLoading=!0,this.message=null,this.size="medium",this.variant="shapes",this.loadingAnimationPath=window.pktAnimationPath,this._shouldDisplayLoader=!1,this.slotController=new u.PktSlotController(this,this.defaultSlot)}connectedCallback(){super.connectedCallback(),this._shouldDisplayLoader=this.delay===0,this.delay>0&&this.setupLoader()}updated(e){e.has("delay")&&this.setupLoader()}render(){const e=d.e({"pkt-loader":!0,[`pkt-loader--${this.inline?"inline":"box"}`]:!0,[`pkt-loader--${this.size}`]:!0}),a=d.e({"pkt-contents":!0,"pkt-hide":this.isLoading});return t.x`<div
|
|
2
|
+
role="status"
|
|
3
|
+
aria-live="polite"
|
|
4
|
+
aria-busy=${this.isLoading?"true":"false"}
|
|
5
|
+
class=${e}
|
|
6
|
+
>
|
|
2
7
|
${this.isLoading&&this._shouldDisplayLoader?t.x`<div class="pkt-loader__spinner">
|
|
3
8
|
<pkt-icon
|
|
4
9
|
name=${this.getVariant(this.variant)}
|
|
@@ -29,7 +29,12 @@ let t = class extends u {
|
|
|
29
29
|
"pkt-contents": !0,
|
|
30
30
|
"pkt-hide": this.isLoading
|
|
31
31
|
});
|
|
32
|
-
return d`<div
|
|
32
|
+
return d`<div
|
|
33
|
+
role="status"
|
|
34
|
+
aria-live="polite"
|
|
35
|
+
aria-busy=${this.isLoading ? "true" : "false"}
|
|
36
|
+
class=${s}
|
|
37
|
+
>
|
|
33
38
|
${this.isLoading && this._shouldDisplayLoader ? d`<div class="pkt-loader__spinner">
|
|
34
39
|
<pkt-icon
|
|
35
40
|
name=${this.getVariant(this.variant)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as b, E as
|
|
1
|
+
import { P as b, E as d, x as n, n as a, a as m } from "./element-CgEWt74-.js";
|
|
2
2
|
import { P as h } from "./pkt-slot-controller-BPGj-LC5.js";
|
|
3
3
|
import { e as f, n as k } from "./ref-BBYSqgeW.js";
|
|
4
4
|
import { e as _ } from "./class-map-BpTj9gtz.js";
|
|
@@ -15,17 +15,17 @@ const x = {
|
|
|
15
15
|
closable: {
|
|
16
16
|
default: !1
|
|
17
17
|
}
|
|
18
|
-
},
|
|
18
|
+
}, u = {
|
|
19
19
|
props: x
|
|
20
20
|
};
|
|
21
|
-
var C = Object.defineProperty, y = Object.getOwnPropertyDescriptor, o = (t, l,
|
|
22
|
-
for (var s = i > 1 ? void 0 : i ? y(l,
|
|
23
|
-
(c = t[r]) && (s = (i ? c(l,
|
|
24
|
-
return i && s && C(l,
|
|
21
|
+
var C = Object.defineProperty, y = Object.getOwnPropertyDescriptor, o = (t, l, p, i) => {
|
|
22
|
+
for (var s = i > 1 ? void 0 : i ? y(l, p) : l, r = t.length - 1, c; r >= 0; r--)
|
|
23
|
+
(c = t[r]) && (s = (i ? c(l, p, s) : c(s)) || s);
|
|
24
|
+
return i && s && C(l, p, s), s;
|
|
25
25
|
};
|
|
26
26
|
let e = class extends b {
|
|
27
27
|
constructor() {
|
|
28
|
-
super(), this.defaultSlot = f(), this.closable =
|
|
28
|
+
super(), this.defaultSlot = f(), this.closable = u.props.closable.default, this.compact = u.props.compact.default, this.title = "", this.skin = u.props.skin.default, this._isClosed = !1, this.close = (t) => {
|
|
29
29
|
this._isClosed = !0, this.dispatchEvent(new CustomEvent("close", { detail: { origin: t }, bubbles: !0 })), this.dispatchEvent(new CustomEvent("on-close", { detail: { origin: t }, bubbles: !0 }));
|
|
30
30
|
}, this.slotController = new h(this, this.defaultSlot), this._isClosed = !1;
|
|
31
31
|
}
|
|
@@ -47,26 +47,27 @@ let e = class extends b {
|
|
|
47
47
|
<button
|
|
48
48
|
@click=${this.close}
|
|
49
49
|
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only"
|
|
50
|
+
aria-label="Lukk"
|
|
50
51
|
>
|
|
51
52
|
<pkt-icon name="close" class="pkt-link__icon"></pkt-icon>
|
|
52
53
|
</button>
|
|
53
|
-
</div>` :
|
|
54
|
-
${this.title ? n`<div class="pkt-messagebox__title">${this.title}</div>` :
|
|
54
|
+
</div>` : d}
|
|
55
|
+
${this.title ? n`<div class="pkt-messagebox__title">${this.title}</div>` : d}
|
|
55
56
|
<div class="pkt-messagebox__text" ${k(this.defaultSlot)}></div>
|
|
56
57
|
</div>`;
|
|
57
58
|
}
|
|
58
59
|
};
|
|
59
60
|
o([
|
|
60
|
-
|
|
61
|
+
a({ type: Boolean, reflect: !0 })
|
|
61
62
|
], e.prototype, "closable", 2);
|
|
62
63
|
o([
|
|
63
|
-
|
|
64
|
+
a({ type: Boolean, reflect: !0 })
|
|
64
65
|
], e.prototype, "compact", 2);
|
|
65
66
|
o([
|
|
66
|
-
|
|
67
|
+
a({ type: String, reflect: !0 })
|
|
67
68
|
], e.prototype, "title", 2);
|
|
68
69
|
o([
|
|
69
|
-
|
|
70
|
+
a({ type: String, reflect: !0 })
|
|
70
71
|
], e.prototype, "skin", 2);
|
|
71
72
|
o([
|
|
72
73
|
v()
|
package/dist/pkt-index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("./alert-7rUOhlNi.cjs"),l=require("./accordionitem-Csh7iSVG.cjs"),d=require("./backlink-JbBNi3qg.cjs"),b=require("./button-B8rdtaHB.cjs"),k=require("./calendar-32W9p9uc.cjs"),m=require("./card-DBlFf1ry.cjs"),g=require("./combobox-DjO0RMUB.cjs"),h=require("./consent-hYeFWNFr.cjs"),f=require("./checkbox-Gn7Wtk9h.cjs"),t=require("./element-6DBpyGQm.cjs"),y=require("./pkt-slot-controller-BzddBp7z.cjs"),s=require("./ref-iJtiv3o2.cjs"),O=require("./class-map-BBG2gMX4.cjs"),j=require("./datepicker-CmTrG5GE.cjs"),q=require("./helptext-CzQX6YVE.cjs"),x=require("./heading-CNycsyMj.cjs"),C=require("./icon-B_ryAy4Q.cjs"),v=require("./input-wrapper-CZ-a00V7.cjs"),S=require("./link-Cjl0xwSq.cjs"),$=require("./linkcard-DqIvb54H.cjs"),L=require("./loader-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("./alert-7rUOhlNi.cjs"),l=require("./accordionitem-Csh7iSVG.cjs"),d=require("./backlink-JbBNi3qg.cjs"),b=require("./button-B8rdtaHB.cjs"),k=require("./calendar-32W9p9uc.cjs"),m=require("./card-DBlFf1ry.cjs"),g=require("./combobox-DjO0RMUB.cjs"),h=require("./consent-hYeFWNFr.cjs"),f=require("./checkbox-Gn7Wtk9h.cjs"),t=require("./element-6DBpyGQm.cjs"),y=require("./pkt-slot-controller-BzddBp7z.cjs"),s=require("./ref-iJtiv3o2.cjs"),O=require("./class-map-BBG2gMX4.cjs"),j=require("./datepicker-CmTrG5GE.cjs"),q=require("./helptext-CzQX6YVE.cjs"),x=require("./heading-CNycsyMj.cjs"),C=require("./icon-B_ryAy4Q.cjs"),v=require("./input-wrapper-CZ-a00V7.cjs"),S=require("./link-Cjl0xwSq.cjs"),$=require("./linkcard-DqIvb54H.cjs"),L=require("./loader-DNidjwH-.cjs"),_=require("./messagebox-CjPtPPrW.cjs"),A=require("./modal-CRtxhCaP.cjs"),B=require("./progressbar-DhMBXkww.cjs"),p=require("./radiobutton-CdT6v1oq.cjs"),T=require("./tag-Bbs0U_Au.cjs"),I=require("./textarea-CPXsMFUq.cjs"),M=require("./textinput-aNI5kibM.cjs"),R=require("./select-Dkl0KhGW.cjs");var H=Object.defineProperty,w=Object.getOwnPropertyDescriptor,o=(a,e,r,i)=>{for(var n=i>1?void 0:i?w(e,r):e,u=a.length-1,c;u>=0;u--)(c=a[u])&&(n=(i?c(e,r,n):c(n))||n);return i&&n&&H(e,r,n),n};exports.PktComponent=class extends t.PktElement{constructor(){super(),this.string="",this.strings=[],this.darkmode=!1,this._list=[],this.defaultSlot=s.e(),this.namedSlot=s.e(),this.slotController=new y.PktSlotController(this,this.defaultSlot,this.namedSlot)}connectedCallback(){this.strings.length&&this.strings.forEach(e=>{this._list.push(e.toUpperCase())}),super.connectedCallback()}render(){const e={"pkt-component":!0,"pkt-component--has-list":this.strings.length>0,"pkt-darkmode":this.darkmode};return t.x`
|
|
2
2
|
<div class="${O.e(e)}">
|
|
3
3
|
<h1 class="pkt-txt-28">${this.string}</h1>
|
|
4
4
|
|
package/dist/pkt-index.js
CHANGED
|
@@ -19,8 +19,8 @@ import { P as Z } from "./icon-CC1js8eR.js";
|
|
|
19
19
|
import { P as et } from "./input-wrapper-Dr__Sxql.js";
|
|
20
20
|
import { P as ot } from "./link-AIyVfcyH.js";
|
|
21
21
|
import { P as at } from "./linkcard-9CNlyT0S.js";
|
|
22
|
-
import { P as it } from "./loader-
|
|
23
|
-
import { P as lt } from "./messagebox-
|
|
22
|
+
import { P as it } from "./loader-h3d-3D7s.js";
|
|
23
|
+
import { P as lt } from "./messagebox-C8KQgCl_.js";
|
|
24
24
|
import { P as mt } from "./modal-Zj8yRX3K.js";
|
|
25
25
|
import { P as ft } from "./progressbar-Dj_mI_A6.js";
|
|
26
26
|
import { P as ht, P as ct } from "./radiobutton-CWxiIVfA.js";
|
package/dist/pkt-loader.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./loader-
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./loader-DNidjwH-.cjs"),t=e.PktLoader;Object.defineProperty(exports,"PktLoader",{enumerable:!0,get:()=>e.PktLoader});exports.default=t;
|
package/dist/pkt-loader.js
CHANGED
package/dist/pkt-messagebox.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./messagebox-
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./messagebox-CjPtPPrW.cjs"),t=e.PktMessagebox;Object.defineProperty(exports,"PktMessagebox",{enumerable:!0,get:()=>e.PktMessagebox});exports.default=t;
|
package/dist/pkt-messagebox.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-elements",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.4",
|
|
4
4
|
"description": "Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo",
|
|
5
5
|
"homepage": "https://punkt.oslo.kommune.no",
|
|
6
6
|
"author": "Team Designsystem, Oslo Origo",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
74
74
|
},
|
|
75
75
|
"license": "MIT",
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "aed226fa38fe77f28a9bc5d0380b194f0f90a64a"
|
|
77
77
|
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { fireEvent } from '@testing-library/dom'
|
|
4
|
+
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
|
|
5
|
+
import { CustomElementFor } from '../../tests/component-registry'
|
|
6
|
+
import { type IPktListbox } from './listbox'
|
|
7
|
+
import './listbox'
|
|
8
|
+
|
|
9
|
+
export interface ListboxTestConfig extends Partial<IPktListbox>, BaseTestConfig {
|
|
10
|
+
label?: string
|
|
11
|
+
id?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Use shared framework
|
|
15
|
+
export const createListboxTest = async (config: ListboxTestConfig = {}) => {
|
|
16
|
+
const { container, element } = await createElementTest<
|
|
17
|
+
CustomElementFor<'pkt-listbox'>,
|
|
18
|
+
ListboxTestConfig
|
|
19
|
+
>('pkt-listbox', { ...config, options: undefined })
|
|
20
|
+
|
|
21
|
+
// Set complex properties directly on the element after creation
|
|
22
|
+
if (config.options) {
|
|
23
|
+
element.options = config.options
|
|
24
|
+
await element.updateComplete
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
container,
|
|
29
|
+
listbox: element,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
expect.extend(toHaveNoViolations)
|
|
34
|
+
|
|
35
|
+
// Cleanup after each test
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
document.body.innerHTML = ''
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('PktListbox', () => {
|
|
41
|
+
describe('Rendering and basic functionality', () => {
|
|
42
|
+
test('renders without errors', async () => {
|
|
43
|
+
const { listbox } = await createListboxTest()
|
|
44
|
+
|
|
45
|
+
expect(listbox).toBeInTheDocument()
|
|
46
|
+
expect(listbox).toBeTruthy()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('renders with options', async () => {
|
|
50
|
+
const options = [
|
|
51
|
+
{ value: 'option1', label: 'Option 1' },
|
|
52
|
+
{ value: 'option2', label: 'Option 2' },
|
|
53
|
+
]
|
|
54
|
+
const { listbox } = await createListboxTest({
|
|
55
|
+
label: 'Test Listbox',
|
|
56
|
+
options,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const listboxElement = listbox.querySelector('.pkt-listbox')
|
|
60
|
+
expect(listboxElement).toBeInTheDocument()
|
|
61
|
+
|
|
62
|
+
const optionElements = listbox.querySelectorAll('.pkt-listbox__option')
|
|
63
|
+
expect(optionElements).toHaveLength(2)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('Properties and attributes', () => {
|
|
68
|
+
test('applies default properties correctly', async () => {
|
|
69
|
+
const { listbox } = await createListboxTest()
|
|
70
|
+
|
|
71
|
+
expect(listbox.isOpen).toBe(false)
|
|
72
|
+
expect(listbox.disabled).toBe(false)
|
|
73
|
+
expect(listbox.includeSearch).toBe(false)
|
|
74
|
+
expect(listbox.isMultiSelect).toBe(false)
|
|
75
|
+
expect(listbox.allowUserInput).toBe(false)
|
|
76
|
+
expect(listbox.maxLength).toBe(0)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('sets properties correctly', async () => {
|
|
80
|
+
const { listbox } = await createListboxTest({
|
|
81
|
+
isOpen: true,
|
|
82
|
+
disabled: true,
|
|
83
|
+
includeSearch: true,
|
|
84
|
+
isMultiSelect: true,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
expect(listbox.isOpen).toBe(true)
|
|
88
|
+
expect(listbox.disabled).toBe(true)
|
|
89
|
+
expect(listbox.includeSearch).toBe(true)
|
|
90
|
+
expect(listbox.isMultiSelect).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('Option handling', () => {
|
|
95
|
+
test('renders single select options correctly', async () => {
|
|
96
|
+
const options = [
|
|
97
|
+
{ value: 'option1', label: 'Option 1', selected: true },
|
|
98
|
+
{ value: 'option2', label: 'Option 2' },
|
|
99
|
+
]
|
|
100
|
+
const { listbox } = await createListboxTest({ options })
|
|
101
|
+
|
|
102
|
+
const selectedOption = listbox.querySelector('.pkt-listbox__option--selected')
|
|
103
|
+
expect(selectedOption).toBeInTheDocument()
|
|
104
|
+
|
|
105
|
+
const checkIcon = listbox.querySelector('pkt-icon[name="check-big"]')
|
|
106
|
+
expect(checkIcon).toBeInTheDocument()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('renders multi-select options with checkboxes', async () => {
|
|
110
|
+
const options = [
|
|
111
|
+
{ value: 'option1', label: 'Option 1', selected: true },
|
|
112
|
+
{ value: 'option2', label: 'Option 2' },
|
|
113
|
+
]
|
|
114
|
+
const { listbox } = await createListboxTest({
|
|
115
|
+
isMultiSelect: true,
|
|
116
|
+
options,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const checkboxes = listbox.querySelectorAll('input[type="checkbox"]')
|
|
120
|
+
expect(checkboxes).toHaveLength(2)
|
|
121
|
+
expect(checkboxes[0]).toBeChecked()
|
|
122
|
+
expect(checkboxes[1]).not.toBeChecked()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('handles option click', async () => {
|
|
126
|
+
const options = [
|
|
127
|
+
{ value: 'option1', label: 'Option 1' },
|
|
128
|
+
{ value: 'option2', label: 'Option 2' },
|
|
129
|
+
]
|
|
130
|
+
const { listbox } = await createListboxTest({ options })
|
|
131
|
+
|
|
132
|
+
// Listen for the option-toggle event
|
|
133
|
+
let toggledValue: string | null = null
|
|
134
|
+
listbox.addEventListener('option-toggle', (e: any) => {
|
|
135
|
+
toggledValue = e.detail
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const optionElement = listbox.querySelector('.pkt-listbox__option')
|
|
139
|
+
fireEvent.click(optionElement!)
|
|
140
|
+
|
|
141
|
+
await listbox.updateComplete
|
|
142
|
+
expect(toggledValue).toBe('option1')
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('Search functionality', () => {
|
|
147
|
+
test('renders search when includeSearch is true', async () => {
|
|
148
|
+
const { listbox } = await createListboxTest({ includeSearch: true })
|
|
149
|
+
|
|
150
|
+
const searchInput = listbox.querySelector('[role="searchbox"]')
|
|
151
|
+
expect(searchInput).toBeInTheDocument()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('filters options based on search', async () => {
|
|
155
|
+
const options = [
|
|
156
|
+
{ value: 'apple', label: 'Apple' },
|
|
157
|
+
{ value: 'banana', label: 'Banana' },
|
|
158
|
+
]
|
|
159
|
+
const { listbox } = await createListboxTest({
|
|
160
|
+
includeSearch: true,
|
|
161
|
+
options,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Set search value and trigger filtering
|
|
165
|
+
listbox.searchValue = 'app'
|
|
166
|
+
listbox.filterOptions()
|
|
167
|
+
await listbox.updateComplete
|
|
168
|
+
|
|
169
|
+
// Should filter to only show Apple - check filtered options
|
|
170
|
+
const visibleOptions = listbox.querySelectorAll('.pkt-listbox__option')
|
|
171
|
+
expect(visibleOptions).toHaveLength(1)
|
|
172
|
+
expect(visibleOptions[0].textContent?.trim()).toContain('Apple')
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
describe('User input functionality', () => {
|
|
177
|
+
test('renders new option banner when allowUserInput is true', async () => {
|
|
178
|
+
const { listbox } = await createListboxTest({
|
|
179
|
+
allowUserInput: true,
|
|
180
|
+
customUserInput: 'New',
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const newOptionBanner = listbox.querySelector('.pkt-listbox__banner--new-option')
|
|
184
|
+
expect(newOptionBanner).toBeInTheDocument()
|
|
185
|
+
expect(newOptionBanner?.getAttribute('data-value')).toBe('New')
|
|
186
|
+
// Check that the text contains the basic structure
|
|
187
|
+
expect(newOptionBanner?.textContent).toMatch(/Legg til.*New/)
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe('Maximum selection', () => {
|
|
192
|
+
test('shows maximum reached banner', async () => {
|
|
193
|
+
const options = [
|
|
194
|
+
{ value: 'option1', label: 'Option 1', selected: true },
|
|
195
|
+
{ value: 'option2', label: 'Option 2', selected: true },
|
|
196
|
+
]
|
|
197
|
+
const { listbox } = await createListboxTest({
|
|
198
|
+
isMultiSelect: true,
|
|
199
|
+
maxLength: 3,
|
|
200
|
+
options,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
const banner = listbox.querySelector('.pkt-listbox__banner--maximum-reached')
|
|
204
|
+
expect(banner).toBeInTheDocument()
|
|
205
|
+
expect(banner?.textContent).toContain('2 av maks 3')
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe('Accessibility', () => {
|
|
210
|
+
test('basic listbox is accessible', async () => {
|
|
211
|
+
const options = [
|
|
212
|
+
{ value: 'option1', label: 'Option 1' },
|
|
213
|
+
{ value: 'option2', label: 'Option 2' },
|
|
214
|
+
]
|
|
215
|
+
const { container } = await createListboxTest({
|
|
216
|
+
label: 'Accessible Listbox',
|
|
217
|
+
options,
|
|
218
|
+
})
|
|
219
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
220
|
+
|
|
221
|
+
const results = await axe(container)
|
|
222
|
+
expect(results).toHaveNoViolations()
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
})
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
|
|
4
|
+
import { CustomElementFor } from '../../tests/component-registry'
|
|
5
|
+
import { type IPktLoader } from './loader'
|
|
6
|
+
import './loader'
|
|
7
|
+
|
|
8
|
+
export interface LoaderTestConfig extends Partial<IPktLoader>, BaseTestConfig {}
|
|
9
|
+
|
|
10
|
+
// Use shared framework
|
|
11
|
+
export const createLoaderTest = async (config: LoaderTestConfig = {}) => {
|
|
12
|
+
const { container, element } = await createElementTest<
|
|
13
|
+
CustomElementFor<'pkt-loader'>,
|
|
14
|
+
LoaderTestConfig
|
|
15
|
+
>('pkt-loader', { ...config, isLoading: undefined })
|
|
16
|
+
|
|
17
|
+
// Set boolean properties directly on the element after creation
|
|
18
|
+
if (config.isLoading !== undefined) {
|
|
19
|
+
element.isLoading = config.isLoading
|
|
20
|
+
await element.updateComplete
|
|
21
|
+
}
|
|
22
|
+
if (config.inline !== undefined) {
|
|
23
|
+
element.inline = config.inline
|
|
24
|
+
await element.updateComplete
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
container,
|
|
29
|
+
loader: element,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
expect.extend(toHaveNoViolations)
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
document.body.innerHTML = ''
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('PktLoader', () => {
|
|
40
|
+
describe('Rendering and basic functionality', () => {
|
|
41
|
+
test('renders without errors', async () => {
|
|
42
|
+
const { loader } = await createLoaderTest()
|
|
43
|
+
|
|
44
|
+
expect(loader).toBeInTheDocument()
|
|
45
|
+
expect(loader).toBeTruthy()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('renders loading state by default', async () => {
|
|
49
|
+
const { loader } = await createLoaderTest()
|
|
50
|
+
|
|
51
|
+
expect(loader.isLoading).toBe(true)
|
|
52
|
+
const spinner = loader.querySelector('.pkt-loader__spinner')
|
|
53
|
+
expect(spinner).toBeInTheDocument()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('renders content when not loading', async () => {
|
|
57
|
+
const { loader } = await createLoaderTest({
|
|
58
|
+
content: '<p>Content</p>',
|
|
59
|
+
isLoading: false,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
expect(loader.isLoading).toBe(false)
|
|
63
|
+
const content = loader.querySelector('.pkt-contents')
|
|
64
|
+
expect(content).toBeInTheDocument()
|
|
65
|
+
expect(content?.classList.contains('pkt-hide')).toBe(false)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('renders with slot content', async () => {
|
|
69
|
+
const { loader } = await createLoaderTest({
|
|
70
|
+
content: '<p>Test content</p>',
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const content = loader.querySelector('p')
|
|
74
|
+
expect(content).toBeInTheDocument()
|
|
75
|
+
expect(content?.textContent).toBe('Test content')
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('Properties and attributes', () => {
|
|
80
|
+
test('applies default properties correctly', async () => {
|
|
81
|
+
const { loader } = await createLoaderTest()
|
|
82
|
+
|
|
83
|
+
expect(loader.isLoading).toBe(true)
|
|
84
|
+
expect(loader.inline).toBe(false)
|
|
85
|
+
expect(loader.size).toBe('medium')
|
|
86
|
+
expect(loader.variant).toBe('shapes')
|
|
87
|
+
expect(loader.delay).toBe(0)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('sets properties correctly', async () => {
|
|
91
|
+
const { loader } = await createLoaderTest({
|
|
92
|
+
size: 'small',
|
|
93
|
+
variant: 'blue',
|
|
94
|
+
delay: 100,
|
|
95
|
+
isLoading: false,
|
|
96
|
+
inline: true,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(loader.isLoading).toBe(false)
|
|
100
|
+
expect(loader.inline).toBe(true)
|
|
101
|
+
expect(loader.size).toBe('small')
|
|
102
|
+
expect(loader.variant).toBe('blue')
|
|
103
|
+
expect(loader.delay).toBe(100)
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe('Size variants', () => {
|
|
108
|
+
test('applies small size correctly', async () => {
|
|
109
|
+
const { loader } = await createLoaderTest({ size: 'small' })
|
|
110
|
+
|
|
111
|
+
const loaderDiv = loader.querySelector('.pkt-loader')
|
|
112
|
+
expect(loaderDiv?.classList.contains('pkt-loader--small')).toBe(true)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('applies medium size correctly', async () => {
|
|
116
|
+
const { loader } = await createLoaderTest({ size: 'medium' })
|
|
117
|
+
|
|
118
|
+
const loaderDiv = loader.querySelector('.pkt-loader')
|
|
119
|
+
expect(loaderDiv?.classList.contains('pkt-loader--medium')).toBe(true)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('applies large size correctly', async () => {
|
|
123
|
+
const { loader } = await createLoaderTest({ size: 'large' })
|
|
124
|
+
|
|
125
|
+
const loaderDiv = loader.querySelector('.pkt-loader')
|
|
126
|
+
expect(loaderDiv?.classList.contains('pkt-loader--large')).toBe(true)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe('Loader variants', () => {
|
|
131
|
+
test('applies shapes variant correctly', async () => {
|
|
132
|
+
const { loader } = await createLoaderTest({ variant: 'shapes' })
|
|
133
|
+
|
|
134
|
+
const icon = loader.querySelector('pkt-icon')
|
|
135
|
+
expect(icon?.getAttribute('name')).toBe('loader')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('applies blue variant correctly', async () => {
|
|
139
|
+
const { loader } = await createLoaderTest({ variant: 'blue' })
|
|
140
|
+
|
|
141
|
+
const icon = loader.querySelector('pkt-icon')
|
|
142
|
+
expect(icon?.getAttribute('name')).toBe('spinner-blue')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('applies rainbow variant correctly', async () => {
|
|
146
|
+
const { loader } = await createLoaderTest({ variant: 'rainbow' })
|
|
147
|
+
|
|
148
|
+
const icon = loader.querySelector('pkt-icon')
|
|
149
|
+
expect(icon?.getAttribute('name')).toBe('spinner')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('Inline mode', () => {
|
|
154
|
+
test('applies box class when inline is false', async () => {
|
|
155
|
+
const { loader } = await createLoaderTest({ inline: false })
|
|
156
|
+
|
|
157
|
+
const loaderDiv = loader.querySelector('.pkt-loader')
|
|
158
|
+
expect(loaderDiv?.classList.contains('pkt-loader--box')).toBe(true)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('applies inline class when inline is true', async () => {
|
|
162
|
+
const { loader } = await createLoaderTest({ inline: true })
|
|
163
|
+
|
|
164
|
+
const loaderDiv = loader.querySelector('.pkt-loader')
|
|
165
|
+
expect(loaderDiv?.classList.contains('pkt-loader--inline')).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('Message functionality', () => {
|
|
170
|
+
test('renders message when provided', async () => {
|
|
171
|
+
const { loader } = await createLoaderTest({ message: 'Loading data...' })
|
|
172
|
+
|
|
173
|
+
const message = loader.querySelector('p')
|
|
174
|
+
expect(message).toBeInTheDocument()
|
|
175
|
+
expect(message?.textContent).toBe('Loading data...')
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('does not render message when not provided', async () => {
|
|
179
|
+
const { loader } = await createLoaderTest()
|
|
180
|
+
|
|
181
|
+
const message = loader.querySelector('p')
|
|
182
|
+
expect(message).not.toBeInTheDocument()
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('Delay functionality', () => {
|
|
187
|
+
test('handles delay correctly', async () => {
|
|
188
|
+
const { loader } = await createLoaderTest({ delay: 100 })
|
|
189
|
+
|
|
190
|
+
// Initially should not show spinner due to delay
|
|
191
|
+
let spinner = loader.querySelector('.pkt-loader__spinner')
|
|
192
|
+
expect(spinner).not.toBeInTheDocument()
|
|
193
|
+
|
|
194
|
+
// Wait for delay to pass
|
|
195
|
+
await new Promise((resolve) => setTimeout(resolve, 150))
|
|
196
|
+
await loader.updateComplete
|
|
197
|
+
|
|
198
|
+
// Now spinner should be visible
|
|
199
|
+
spinner = loader.querySelector('.pkt-loader__spinner')
|
|
200
|
+
expect(spinner).toBeInTheDocument()
|
|
201
|
+
}, 300)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
describe('Content visibility', () => {
|
|
205
|
+
test('hides content when loading', async () => {
|
|
206
|
+
const { loader } = await createLoaderTest({
|
|
207
|
+
isLoading: true,
|
|
208
|
+
content: '<p>Content</p>',
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
const content = loader.querySelector('.pkt-contents')
|
|
212
|
+
expect(content?.classList.contains('pkt-hide')).toBe(true)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('shows content when not loading', async () => {
|
|
216
|
+
const { loader } = await createLoaderTest({
|
|
217
|
+
isLoading: false,
|
|
218
|
+
content: '<p>Content</p>',
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const content = loader.querySelector('.pkt-contents')
|
|
222
|
+
expect(content?.classList.contains('pkt-hide')).toBe(false)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('ARIA and accessibility', () => {
|
|
227
|
+
test('has correct ARIA attributes', async () => {
|
|
228
|
+
const { loader } = await createLoaderTest()
|
|
229
|
+
|
|
230
|
+
const loaderDiv = loader.querySelector('.pkt-loader')
|
|
231
|
+
expect(loaderDiv?.getAttribute('role')).toBe('status')
|
|
232
|
+
expect(loaderDiv?.getAttribute('aria-live')).toBe('polite')
|
|
233
|
+
// Check the aria-busy attribute
|
|
234
|
+
expect(loaderDiv?.getAttribute('aria-busy')).toBe('true')
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('updates aria-busy when loading state changes', async () => {
|
|
238
|
+
const { loader } = await createLoaderTest({ isLoading: false })
|
|
239
|
+
|
|
240
|
+
const loaderDiv = loader.querySelector('.pkt-loader')
|
|
241
|
+
// Check the aria-busy attribute
|
|
242
|
+
expect(loaderDiv?.getAttribute('aria-busy')).toBe('false')
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
describe('Accessibility', () => {
|
|
247
|
+
test('loader is accessible', async () => {
|
|
248
|
+
const { container } = await createLoaderTest({
|
|
249
|
+
message: 'Loading content',
|
|
250
|
+
content: '<p>Content</p>',
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const results = await axe(container)
|
|
254
|
+
expect(results).toHaveNoViolations()
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
})
|
|
@@ -94,7 +94,12 @@ export class PktLoader extends PktElement implements IPktLoader {
|
|
|
94
94
|
'pkt-hide': this.isLoading,
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
-
return html`<div
|
|
97
|
+
return html`<div
|
|
98
|
+
role="status"
|
|
99
|
+
aria-live="polite"
|
|
100
|
+
aria-busy=${this.isLoading ? 'true' : 'false'}
|
|
101
|
+
class=${classes}
|
|
102
|
+
>
|
|
98
103
|
${this.isLoading && this._shouldDisplayLoader
|
|
99
104
|
? html`<div class="pkt-loader__spinner">
|
|
100
105
|
<pkt-icon
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { fireEvent } from '@testing-library/dom'
|
|
4
|
+
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
|
|
5
|
+
import { CustomElementFor } from '../../tests/component-registry'
|
|
6
|
+
import { type IPktMessagebox } from './messagebox'
|
|
7
|
+
import './messagebox'
|
|
8
|
+
|
|
9
|
+
export interface MessageboxTestConfig extends Partial<IPktMessagebox>, BaseTestConfig {}
|
|
10
|
+
|
|
11
|
+
// Use shared framework
|
|
12
|
+
export const createMessageboxTest = async (config: MessageboxTestConfig = {}) => {
|
|
13
|
+
const { container, element } = await createElementTest<
|
|
14
|
+
CustomElementFor<'pkt-messagebox'>,
|
|
15
|
+
MessageboxTestConfig
|
|
16
|
+
>('pkt-messagebox', config)
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
container,
|
|
20
|
+
messagebox: element,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
expect.extend(toHaveNoViolations)
|
|
25
|
+
|
|
26
|
+
// Cleanup after each test
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
document.body.innerHTML = ''
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('PktMessagebox', () => {
|
|
32
|
+
describe('Rendering and basic functionality', () => {
|
|
33
|
+
test('renders without errors', async () => {
|
|
34
|
+
const { messagebox } = await createMessageboxTest()
|
|
35
|
+
|
|
36
|
+
expect(messagebox).toBeInTheDocument()
|
|
37
|
+
expect(messagebox).toBeTruthy()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('renders with basic structure', async () => {
|
|
41
|
+
const { messagebox } = await createMessageboxTest({
|
|
42
|
+
title: 'Test Title',
|
|
43
|
+
content: 'Test content',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
47
|
+
expect(messageboxDiv).toBeInTheDocument()
|
|
48
|
+
|
|
49
|
+
const title = messagebox.querySelector('.pkt-messagebox__title')
|
|
50
|
+
expect(title).toBeInTheDocument()
|
|
51
|
+
expect(title?.textContent).toBe('Test Title')
|
|
52
|
+
|
|
53
|
+
const text = messagebox.querySelector('.pkt-messagebox__text')
|
|
54
|
+
expect(text).toBeInTheDocument()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('renders content correctly', async () => {
|
|
58
|
+
const { messagebox } = await createMessageboxTest({
|
|
59
|
+
content: 'This is the message content',
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const text = messagebox.querySelector('.pkt-messagebox__text')
|
|
63
|
+
expect(text?.textContent).toContain('This is the message content')
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('Properties and attributes', () => {
|
|
68
|
+
test('applies default properties correctly', async () => {
|
|
69
|
+
const { messagebox } = await createMessageboxTest()
|
|
70
|
+
|
|
71
|
+
expect(messagebox.closable).toBe(false)
|
|
72
|
+
expect(messagebox.compact).toBe(false)
|
|
73
|
+
expect(messagebox.title).toBe('')
|
|
74
|
+
expect(messagebox.skin).toBe('beige')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('sets properties correctly', async () => {
|
|
78
|
+
const { messagebox } = await createMessageboxTest({
|
|
79
|
+
closable: true,
|
|
80
|
+
compact: true,
|
|
81
|
+
title: 'Custom Title',
|
|
82
|
+
skin: 'blue',
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
expect(messagebox.closable).toBe(true)
|
|
86
|
+
expect(messagebox.compact).toBe(true)
|
|
87
|
+
expect(messagebox.title).toBe('Custom Title')
|
|
88
|
+
expect(messagebox.skin).toBe('blue')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('Skin variants', () => {
|
|
93
|
+
test('applies beige skin correctly', async () => {
|
|
94
|
+
const { messagebox } = await createMessageboxTest({ skin: 'beige' })
|
|
95
|
+
|
|
96
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
97
|
+
expect(messageboxDiv?.classList.contains('pkt-messagebox--beige')).toBe(true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('applies blue skin correctly', async () => {
|
|
101
|
+
const { messagebox } = await createMessageboxTest({ skin: 'blue' })
|
|
102
|
+
|
|
103
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
104
|
+
expect(messageboxDiv?.classList.contains('pkt-messagebox--blue')).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('applies red skin correctly', async () => {
|
|
108
|
+
const { messagebox } = await createMessageboxTest({ skin: 'red' })
|
|
109
|
+
|
|
110
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
111
|
+
expect(messageboxDiv?.classList.contains('pkt-messagebox--red')).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('applies green skin correctly', async () => {
|
|
115
|
+
const { messagebox } = await createMessageboxTest({ skin: 'green' })
|
|
116
|
+
|
|
117
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
118
|
+
expect(messageboxDiv?.classList.contains('pkt-messagebox--green')).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('Compact mode', () => {
|
|
123
|
+
test('applies compact class when compact is true', async () => {
|
|
124
|
+
const { messagebox } = await createMessageboxTest({ compact: true })
|
|
125
|
+
|
|
126
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
127
|
+
expect(messageboxDiv?.classList.contains('pkt-messagebox--compact')).toBe(true)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('does not apply compact class when compact is false', async () => {
|
|
131
|
+
const { messagebox } = await createMessageboxTest({ compact: false })
|
|
132
|
+
|
|
133
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
134
|
+
expect(messageboxDiv?.classList.contains('pkt-messagebox--compact')).toBe(false)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('Closable functionality', () => {
|
|
139
|
+
test('renders close button when closable is true', async () => {
|
|
140
|
+
const { messagebox } = await createMessageboxTest({ closable: true })
|
|
141
|
+
|
|
142
|
+
const closeButton = messagebox.querySelector('.pkt-messagebox__close button')
|
|
143
|
+
expect(closeButton).toBeInTheDocument()
|
|
144
|
+
|
|
145
|
+
const closeIcon = messagebox.querySelector('pkt-icon[name="close"]')
|
|
146
|
+
expect(closeIcon).toBeInTheDocument()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('does not render close button when closable is false', async () => {
|
|
150
|
+
const { messagebox } = await createMessageboxTest({ closable: false })
|
|
151
|
+
|
|
152
|
+
const closeButton = messagebox.querySelector('.pkt-messagebox__close')
|
|
153
|
+
expect(closeButton).not.toBeInTheDocument()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('closes messagebox when close button is clicked', async () => {
|
|
157
|
+
const { messagebox } = await createMessageboxTest({ closable: true })
|
|
158
|
+
|
|
159
|
+
const closeButton = messagebox.querySelector('.pkt-messagebox__close button')
|
|
160
|
+
fireEvent.click(closeButton!)
|
|
161
|
+
|
|
162
|
+
await messagebox.updateComplete
|
|
163
|
+
expect(messagebox._isClosed).toBe(true)
|
|
164
|
+
expect(messagebox.classList.contains('pkt-hide')).toBe(true)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('dispatches close events when closed', async () => {
|
|
168
|
+
const { messagebox } = await createMessageboxTest({ closable: true })
|
|
169
|
+
|
|
170
|
+
const closeHandler = jest.fn()
|
|
171
|
+
const onCloseHandler = jest.fn()
|
|
172
|
+
messagebox.addEventListener('close', closeHandler)
|
|
173
|
+
messagebox.addEventListener('on-close', onCloseHandler)
|
|
174
|
+
|
|
175
|
+
const closeButton = messagebox.querySelector('.pkt-messagebox__close button')
|
|
176
|
+
fireEvent.click(closeButton!)
|
|
177
|
+
|
|
178
|
+
expect(closeHandler).toHaveBeenCalled()
|
|
179
|
+
expect(onCloseHandler).toHaveBeenCalled()
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('Title functionality', () => {
|
|
184
|
+
test('renders title when provided', async () => {
|
|
185
|
+
const { messagebox } = await createMessageboxTest({ title: 'Important Message' })
|
|
186
|
+
|
|
187
|
+
const title = messagebox.querySelector('.pkt-messagebox__title')
|
|
188
|
+
expect(title).toBeInTheDocument()
|
|
189
|
+
expect(title?.textContent).toBe('Important Message')
|
|
190
|
+
expect(title?.textContent).toBe('Important Message')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('does not render title when empty', async () => {
|
|
194
|
+
const { messagebox } = await createMessageboxTest({ title: '' })
|
|
195
|
+
|
|
196
|
+
const title = messagebox.querySelector('.pkt-messagebox__title')
|
|
197
|
+
expect(title).not.toBeInTheDocument()
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('CSS classes', () => {
|
|
202
|
+
test('applies base messagebox class', async () => {
|
|
203
|
+
const { messagebox } = await createMessageboxTest()
|
|
204
|
+
|
|
205
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
206
|
+
expect(messageboxDiv?.classList.contains('pkt-messagebox')).toBe(true)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('applies closable class when closable', async () => {
|
|
210
|
+
const { messagebox } = await createMessageboxTest({ closable: true })
|
|
211
|
+
|
|
212
|
+
const messageboxDiv = messagebox.querySelector('.pkt-messagebox')
|
|
213
|
+
expect(messageboxDiv?.classList.contains('pkt-messagebox--closable')).toBe(true)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('Accessibility', () => {
|
|
218
|
+
test('basic messagebox is accessible', async () => {
|
|
219
|
+
const { container } = await createMessageboxTest({
|
|
220
|
+
title: 'Accessible Message',
|
|
221
|
+
content: 'This is an accessible message',
|
|
222
|
+
})
|
|
223
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
224
|
+
|
|
225
|
+
const results = await axe(container)
|
|
226
|
+
expect(results).toHaveNoViolations()
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('closable messagebox is accessible', async () => {
|
|
230
|
+
const { container } = await createMessageboxTest({
|
|
231
|
+
closable: true,
|
|
232
|
+
title: 'Closable Message',
|
|
233
|
+
content: 'This message can be closed',
|
|
234
|
+
})
|
|
235
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
236
|
+
|
|
237
|
+
const results = await axe(container)
|
|
238
|
+
expect(results).toHaveNoViolations()
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
})
|
|
@@ -65,6 +65,7 @@ export class PktMessagebox extends PktElement implements IPktMessagebox {
|
|
|
65
65
|
<button
|
|
66
66
|
@click=${this.close}
|
|
67
67
|
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only"
|
|
68
|
+
aria-label="Lukk"
|
|
68
69
|
>
|
|
69
70
|
<pkt-icon name="close" class="pkt-link__icon"></pkt-icon>
|
|
70
71
|
</button>
|