@lilydesignsystem/angular-link-picker 0.1.1 → 0.2.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/{dist/fesm2022 → fesm2022}/lilydesignsystem-angular-link-picker.mjs +48 -5
- package/fesm2022/lilydesignsystem-angular-link-picker.mjs.map +1 -0
- package/package.json +20 -11
- package/{dist/types → types}/lilydesignsystem-angular-link-picker.d.ts +29 -13
- package/types/lilydesignsystem-angular-link-picker.d.ts.map +1 -0
- package/AGENTS.md +0 -29
- package/CHANGELOG.md +0 -8
- package/dist/fesm2022/lilydesignsystem-angular-link-picker.mjs.map +0 -1
- package/dist/package.json +0 -50
- package/dist/types/lilydesignsystem-angular-link-picker.d.ts.map +0 -1
- package/docs/accessibility.md +0 -29
- package/examples/README.md +0 -9
- package/examples/basic.component.ts +0 -18
- package/examples/custom-icon.component.ts +0 -22
- package/examples/router.component.ts +0 -22
- package/index.md +0 -86
- package/index.ts +0 -11
- package/link-picker.component.spec.ts +0 -287
- package/link-picker.component.ts +0 -398
- package/ng-package.json +0 -10
- package/spec/index.md +0 -102
- package/tsconfig.lib.json +0 -13
- /package/{dist/README.md → README.md} +0 -0
package/index.md
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# LinkPicker (Angular)
|
|
2
|
-
|
|
3
|
-
A headless control for **page links**: a single-icon button (a bundled home SVG) that opens a dropdown of links **your app
|
|
4
|
-
defines** — "Home", "About Us", "Contact Us", "Privacy Policy". No CSS, no routes, no English.
|
|
5
|
-
|
|
6
|
-
The single source of truth is [spec/index.md](./spec/index.md); this file is the guide.
|
|
7
|
-
|
|
8
|
-
## Quick start
|
|
9
|
-
|
|
10
|
-
```ts
|
|
11
|
-
import { Component } from "@angular/core";
|
|
12
|
-
import { LinkPicker, type LinkItem } from "@lilydesignsystem/angular-link-picker";
|
|
13
|
-
|
|
14
|
-
@Component({
|
|
15
|
-
selector: "app-header",
|
|
16
|
-
standalone: true,
|
|
17
|
-
imports: [LinkPicker],
|
|
18
|
-
template: `<lily-link-picker label="Pages" [links]="links" />`,
|
|
19
|
-
})
|
|
20
|
-
export class AppHeader {
|
|
21
|
-
links: LinkItem[] = [
|
|
22
|
-
{ label: "Home", href: "/" },
|
|
23
|
-
{ label: "About Us", href: "/about/" },
|
|
24
|
-
{ label: "Contact Us", href: "/contact/" },
|
|
25
|
-
{ label: "Privacy Policy", href: "/privacy/" },
|
|
26
|
-
];
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
`label` names the button, the list and the tooltip. There is **no default list**: the package ships no routes and no English.
|
|
31
|
-
|
|
32
|
-
## Client-side navigation
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
import { Component, inject } from "@angular/core";
|
|
36
|
-
import { Router } from "@angular/router";
|
|
37
|
-
import { LinkPicker } from "@lilydesignsystem/angular-link-picker";
|
|
38
|
-
|
|
39
|
-
@Component({
|
|
40
|
-
selector: "app-header",
|
|
41
|
-
standalone: true,
|
|
42
|
-
imports: [LinkPicker],
|
|
43
|
-
template: `<lily-link-picker label="Pages" [links]="links()" [navigate]="go" />`,
|
|
44
|
-
})
|
|
45
|
-
export class AppHeader {
|
|
46
|
-
private router = inject(Router);
|
|
47
|
-
go = (href: string) => void this.router.navigateByUrl(href);
|
|
48
|
-
links = () => [
|
|
49
|
-
{ label: "Home", href: "/" },
|
|
50
|
-
{ label: "About Us", href: "/about/" },
|
|
51
|
-
{ label: "Contact Us", href: "/contact/" },
|
|
52
|
-
{ label: "Privacy Policy", href: "/privacy/" },
|
|
53
|
-
].map((p) => ({ ...p, current: this.router.url === p.href }));
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Ctrl/Cmd/Shift/Alt-click, middle-click and `newTab` links always stay native browser behaviour.
|
|
58
|
-
|
|
59
|
-
## `LinkItem`
|
|
60
|
-
|
|
61
|
-
| Field | Notes |
|
|
62
|
-
| --- | --- |
|
|
63
|
-
| label | Visible text. Yours, so it localises. |
|
|
64
|
-
| href | A route (`"/about/"`) or a full URL. |
|
|
65
|
-
| id | Reported when a link is chosen; defaults to the href. |
|
|
66
|
-
| current | Marks the current page with `aria-current="page"`. |
|
|
67
|
-
| newTab | `target="_blank"` with `rel="noopener noreferrer"`. |
|
|
68
|
-
|
|
69
|
-
## Props
|
|
70
|
-
|
|
71
|
-
| `label` | `input.required<string>()` | yes | Accessible name of the button **and** the list; also the tooltip text. |
|
|
72
|
-
| `links` | `input.required<LinkItem[]>()` | yes | `{ id?, label, href, current?, newTab? }`. `id` defaults to `href`. |
|
|
73
|
-
| `navigate` | `input<(href: string) => void>()` | no | Client-side navigation hook (a router's `navigateByUrl`). |
|
|
74
|
-
| `navigated` | `output<{ id, href }>()` | no | Emitted after a link is chosen (the Svelte `onNavigate` callback). |
|
|
75
|
-
| icon template | `<ng-template lilyLinkPickerIcon let-args>` | no | Replaces the **icon**, not the links. |
|
|
76
|
-
| `className` | `input<string>()` | no | Appended to the root class. |
|
|
77
|
-
|
|
78
|
-
## Styling
|
|
79
|
-
|
|
80
|
-
Headless: no CSS ships. Hooks: `.link-picker`, `.link-picker-button`, `.link-picker-icon`, `.link-picker-tooltip`,
|
|
81
|
-
`.link-picker-list` (carries `hidden` when closed), `.link-picker-list-item`, `.link-picker-link`. The monorepo's 45 `themes/`
|
|
82
|
-
style them all, including the popup placement.
|
|
83
|
-
|
|
84
|
-
## Examples
|
|
85
|
-
|
|
86
|
-
[examples/](./examples/README.md): the four-link example, router integration and a custom icon.
|
package/index.ts
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
import { Component } from "@angular/core";
|
|
2
|
-
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|
3
|
-
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
5
|
-
import { resolve } from "node:path";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
LinkPicker,
|
|
9
|
-
LinkPickerIcon,
|
|
10
|
-
linkId,
|
|
11
|
-
nextLinkPickerId,
|
|
12
|
-
type LinkItem,
|
|
13
|
-
} from "./link-picker.component";
|
|
14
|
-
|
|
15
|
-
const LINKS: LinkItem[] = [
|
|
16
|
-
{ label: "Home", href: "/" },
|
|
17
|
-
{ label: "About Us", href: "/about/" },
|
|
18
|
-
{ label: "Contact Us", href: "/contact/" },
|
|
19
|
-
{ label: "Privacy Policy", href: "/privacy/" },
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const flush = () => new Promise<void>((r) => setTimeout(r, 0));
|
|
23
|
-
|
|
24
|
-
let fixtures: ComponentFixture<unknown>[] = [];
|
|
25
|
-
|
|
26
|
-
function mount(inputs: Record<string, unknown> = {}): ComponentFixture<LinkPicker> {
|
|
27
|
-
const fixture = TestBed.createComponent(LinkPicker);
|
|
28
|
-
fixture.componentRef.setInput("label", "Pages");
|
|
29
|
-
fixture.componentRef.setInput("links", LINKS);
|
|
30
|
-
for (const [k, v] of Object.entries(inputs)) fixture.componentRef.setInput(k, v);
|
|
31
|
-
fixture.detectChanges();
|
|
32
|
-
fixtures.push(fixture);
|
|
33
|
-
return fixture;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const q = <T extends Element>(f: ComponentFixture<unknown>, sel: string) =>
|
|
37
|
-
f.nativeElement.querySelector(sel) as T;
|
|
38
|
-
const trigger = (f: ComponentFixture<unknown>) => q<HTMLButtonElement>(f, ".link-picker-button");
|
|
39
|
-
const list = (f: ComponentFixture<unknown>) => q<HTMLUListElement>(f, ".link-picker-list");
|
|
40
|
-
const links = (f: ComponentFixture<unknown>) =>
|
|
41
|
-
Array.from(f.nativeElement.querySelectorAll(".link-picker-link")) as HTMLAnchorElement[];
|
|
42
|
-
|
|
43
|
-
function press(f: ComponentFixture<unknown>, target: HTMLElement, key: string): void {
|
|
44
|
-
target.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true }));
|
|
45
|
-
f.detectChanges();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function open(f: ComponentFixture<unknown>): Promise<void> {
|
|
49
|
-
trigger(f).dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
50
|
-
f.detectChanges();
|
|
51
|
-
await flush();
|
|
52
|
-
f.detectChanges();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
afterEach(() => {
|
|
56
|
-
for (const f of fixtures) f.destroy();
|
|
57
|
-
fixtures = [];
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe("LinkPicker", () => {
|
|
61
|
-
test("7.1 renders the root, button, tooltip and a hidden list", () => {
|
|
62
|
-
const f = mount();
|
|
63
|
-
expect(q(f, "div.link-picker")).toBeTruthy();
|
|
64
|
-
expect(trigger(f)).toBeTruthy();
|
|
65
|
-
expect(q(f, ".link-picker-tooltip")).toBeTruthy();
|
|
66
|
-
expect(list(f).hasAttribute("hidden")).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("7.2 the button is named by label with aria-expanded and aria-controls", () => {
|
|
70
|
-
const f = mount();
|
|
71
|
-
expect(trigger(f).getAttribute("aria-label")).toBe("Pages");
|
|
72
|
-
expect(trigger(f).getAttribute("aria-expanded")).toBe("false");
|
|
73
|
-
expect(trigger(f).getAttribute("aria-controls")).toBe(list(f).id);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("7.3 the default icon is an aria-hidden svg; a projected template replaces it", () => {
|
|
77
|
-
const f = mount();
|
|
78
|
-
const svg = q<SVGElement>(f, "svg.link-picker-icon");
|
|
79
|
-
expect(svg.getAttribute("aria-hidden")).toBe("true");
|
|
80
|
-
expect(svg.getAttribute("viewBox")).toBe("0 0 16 16");
|
|
81
|
-
|
|
82
|
-
@Component({
|
|
83
|
-
standalone: true,
|
|
84
|
-
imports: [LinkPicker, LinkPickerIcon],
|
|
85
|
-
template: `<lily-link-picker label="Pages" [links]="links"><ng-template lilyLinkPickerIcon let-args><span class="mine">{{ args.open ? "-" : "+" }}</span></ng-template></lily-link-picker>`,
|
|
86
|
-
})
|
|
87
|
-
class Host {
|
|
88
|
-
links = LINKS;
|
|
89
|
-
}
|
|
90
|
-
const hf = TestBed.createComponent(Host);
|
|
91
|
-
hf.detectChanges();
|
|
92
|
-
fixtures.push(hf);
|
|
93
|
-
expect(hf.nativeElement.querySelector(".mine")).toBeTruthy();
|
|
94
|
-
expect(hf.nativeElement.querySelector("svg.link-picker-icon")).toBeNull();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("7.4 one real link per entry, in order", () => {
|
|
98
|
-
expect(links(mount()).map((a) => [a.textContent?.trim(), a.getAttribute("href")])).toEqual([
|
|
99
|
-
["Home", "/"],
|
|
100
|
-
["About Us", "/about/"],
|
|
101
|
-
["Contact Us", "/contact/"],
|
|
102
|
-
["Privacy Policy", "/privacy/"],
|
|
103
|
-
]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("7.5 no links are invented", () => {
|
|
107
|
-
expect(links(mount({ links: [] }))).toHaveLength(0);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("7.6 the list is named with label", () => {
|
|
111
|
-
expect(list(mount()).getAttribute("aria-label")).toBe("Pages");
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("7.7 click opens the list and focuses the first link", async () => {
|
|
115
|
-
const f = mount();
|
|
116
|
-
await open(f);
|
|
117
|
-
expect(list(f).hasAttribute("hidden")).toBe(false);
|
|
118
|
-
expect(trigger(f).getAttribute("aria-expanded")).toBe("true");
|
|
119
|
-
expect(document.activeElement).toBe(links(f)[0]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("7.8 a second click closes", async () => {
|
|
123
|
-
const f = mount();
|
|
124
|
-
await open(f);
|
|
125
|
-
await open(f);
|
|
126
|
-
expect(list(f).hasAttribute("hidden")).toBe(true);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("7.9 ArrowDown opens at the first link, ArrowUp at the last", async () => {
|
|
130
|
-
const a = mount();
|
|
131
|
-
press(a, trigger(a), "ArrowDown");
|
|
132
|
-
await flush();
|
|
133
|
-
expect(document.activeElement).toBe(links(a)[0]);
|
|
134
|
-
const b = mount();
|
|
135
|
-
press(b, trigger(b), "ArrowUp");
|
|
136
|
-
await flush();
|
|
137
|
-
expect(document.activeElement).toBe(links(b)[3]);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("7.10 arrows move and clamp; Home and End jump", async () => {
|
|
141
|
-
const f = mount();
|
|
142
|
-
await open(f);
|
|
143
|
-
press(f, list(f), "ArrowUp");
|
|
144
|
-
expect(document.activeElement).toBe(links(f)[0]);
|
|
145
|
-
press(f, list(f), "ArrowDown");
|
|
146
|
-
expect(document.activeElement).toBe(links(f)[1]);
|
|
147
|
-
press(f, list(f), "End");
|
|
148
|
-
expect(document.activeElement).toBe(links(f)[3]);
|
|
149
|
-
press(f, list(f), "ArrowDown");
|
|
150
|
-
expect(document.activeElement).toBe(links(f)[3]);
|
|
151
|
-
press(f, list(f), "Home");
|
|
152
|
-
expect(document.activeElement).toBe(links(f)[0]);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("7.11 Escape closes and returns focus to the button", async () => {
|
|
156
|
-
const f = mount();
|
|
157
|
-
await open(f);
|
|
158
|
-
press(f, list(f), "Escape");
|
|
159
|
-
await flush();
|
|
160
|
-
f.detectChanges();
|
|
161
|
-
expect(list(f).hasAttribute("hidden")).toBe(true);
|
|
162
|
-
expect(document.activeElement).toBe(trigger(f));
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("7.12 Tab closes without sending focus to the document top", async () => {
|
|
166
|
-
const f = mount();
|
|
167
|
-
await open(f);
|
|
168
|
-
press(f, list(f), "Tab");
|
|
169
|
-
await flush();
|
|
170
|
-
expect(list(f).hasAttribute("hidden")).toBe(true);
|
|
171
|
-
expect(document.activeElement).toBe(trigger(f));
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("7.13 clicking outside closes", async () => {
|
|
175
|
-
const f = mount();
|
|
176
|
-
await open(f);
|
|
177
|
-
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
178
|
-
f.detectChanges();
|
|
179
|
-
expect(list(f).hasAttribute("hidden")).toBe(true);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("7.14 current marks only that link aria-current=page", () => {
|
|
183
|
-
const f = mount({ links: [LINKS[0], { ...LINKS[1], current: true }, LINKS[2]] });
|
|
184
|
-
expect(links(f).map((a) => a.getAttribute("aria-current"))).toEqual([null, "page", null]);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test("7.15 newTab adds target and rel", () => {
|
|
188
|
-
const f = mount({ links: [{ label: "Docs", href: "https://example.test/", newTab: true }, LINKS[0]] });
|
|
189
|
-
expect(links(f)[0].getAttribute("target")).toBe("_blank");
|
|
190
|
-
expect(links(f)[0].getAttribute("rel")).toBe("noopener noreferrer");
|
|
191
|
-
expect(links(f)[1].hasAttribute("target")).toBe(false);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("7.16 the navigated output reports the id, else the href", async () => {
|
|
195
|
-
const f = mount({ links: [{ id: "about", label: "About Us", href: "/about/" }, LINKS[0]] });
|
|
196
|
-
const seen: unknown[] = [];
|
|
197
|
-
f.componentInstance.navigated.subscribe((e) => seen.push(e));
|
|
198
|
-
await open(f);
|
|
199
|
-
for (const a of links(f)) a.addEventListener("click", (e) => e.preventDefault());
|
|
200
|
-
links(f)[0].dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, button: 0 }));
|
|
201
|
-
f.detectChanges();
|
|
202
|
-
await open(f);
|
|
203
|
-
links(f)[1].dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, button: 0 }));
|
|
204
|
-
expect(seen).toEqual([{ id: "about", href: "/about/" }, { id: "/", href: "/" }]);
|
|
205
|
-
expect(linkId({ label: "x", href: "/y" })).toBe("/y");
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("7.17 navigate handles a plain left click only", async () => {
|
|
209
|
-
const navigate = vi.fn();
|
|
210
|
-
const f = mount({ navigate, links: [LINKS[1], { label: "Docs", href: "https://example.test/", newTab: true }] });
|
|
211
|
-
await open(f);
|
|
212
|
-
const plain = new MouseEvent("click", { bubbles: true, cancelable: true, button: 0 });
|
|
213
|
-
links(f)[0].dispatchEvent(plain);
|
|
214
|
-
expect(navigate).toHaveBeenCalledWith("/about/");
|
|
215
|
-
expect(plain.defaultPrevented).toBe(true);
|
|
216
|
-
navigate.mockClear();
|
|
217
|
-
f.detectChanges();
|
|
218
|
-
await open(f);
|
|
219
|
-
const ctrl = new MouseEvent("click", { bubbles: true, cancelable: true, button: 0, ctrlKey: true });
|
|
220
|
-
links(f)[0].dispatchEvent(ctrl);
|
|
221
|
-
expect(navigate).not.toHaveBeenCalled();
|
|
222
|
-
expect(ctrl.defaultPrevented).toBe(false);
|
|
223
|
-
f.detectChanges();
|
|
224
|
-
await open(f);
|
|
225
|
-
const tab = new MouseEvent("click", { bubbles: true, cancelable: true, button: 0 });
|
|
226
|
-
links(f)[1].dispatchEvent(tab);
|
|
227
|
-
expect(navigate).not.toHaveBeenCalled();
|
|
228
|
-
expect(tab.defaultPrevented).toBe(false);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test("7.18 without navigate a click is left to the browser", async () => {
|
|
232
|
-
const f = mount();
|
|
233
|
-
await open(f);
|
|
234
|
-
const ev = new MouseEvent("click", { bubbles: true, cancelable: true, button: 0 });
|
|
235
|
-
links(f)[0].dispatchEvent(ev);
|
|
236
|
-
expect(ev.defaultPrevented).toBe(false);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
test("7.19 the tooltip is a hidden role=tooltip holding the label", () => {
|
|
240
|
-
const tip = q<HTMLElement>(mount(), ".link-picker-tooltip");
|
|
241
|
-
expect(tip.getAttribute("role")).toBe("tooltip");
|
|
242
|
-
expect(tip.textContent).toBe("Pages");
|
|
243
|
-
expect(tip.hasAttribute("hidden")).toBe(true);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test("7.20 the tooltip shows on hover, hides on Escape, and never shows while open", async () => {
|
|
247
|
-
const f = mount();
|
|
248
|
-
const tip = q<HTMLElement>(f, ".link-picker-tooltip");
|
|
249
|
-
const host = q<HTMLElement>(f, "lily-icon-button");
|
|
250
|
-
host.dispatchEvent(new MouseEvent("mouseenter"));
|
|
251
|
-
f.detectChanges();
|
|
252
|
-
expect(tip.hasAttribute("hidden")).toBe(false);
|
|
253
|
-
await flush();
|
|
254
|
-
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
|
|
255
|
-
f.detectChanges();
|
|
256
|
-
expect(tip.hasAttribute("hidden")).toBe(true);
|
|
257
|
-
host.dispatchEvent(new MouseEvent("mouseleave"));
|
|
258
|
-
host.dispatchEvent(new MouseEvent("mouseenter"));
|
|
259
|
-
f.detectChanges();
|
|
260
|
-
expect(tip.hasAttribute("hidden")).toBe(false);
|
|
261
|
-
await open(f);
|
|
262
|
-
expect(tip.hasAttribute("hidden")).toBe(true);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test("7.21 the tooltip is not wired with aria-describedby", () => {
|
|
266
|
-
expect(trigger(mount()).hasAttribute("aria-describedby")).toBe(false);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
test("7.22 className is appended to the root", () => {
|
|
270
|
-
expect(q(mount({ className: "extra" }), ".link-picker").classList.contains("extra")).toBe(true);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("7.23 two instances get distinct ids", () => {
|
|
274
|
-
expect(nextLinkPickerId()).not.toBe(nextLinkPickerId());
|
|
275
|
-
expect(list(mount()).id).not.toBe(list(mount()).id);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
test("7.24 the source ships no stylesheet, inline style, English default or route", () => {
|
|
279
|
-
const src = readFileSync(resolve(__dirname, "link-picker.component.ts"), "utf8")
|
|
280
|
-
.replace(/<!--[\s\S]*?-->/g, "")
|
|
281
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
282
|
-
.replace(/^\s*\/\/.*$/gm, "");
|
|
283
|
-
expect(src).not.toMatch(/styles\s*:|<style|\sstyle=/);
|
|
284
|
-
expect(src).not.toMatch(/(label|href):\s*["'`]/);
|
|
285
|
-
expect(src).not.toMatch(/href="\//);
|
|
286
|
-
});
|
|
287
|
-
});
|