web: unit tests for the simple things, with fixes that the tests revealed (#11633)
* Added tests and refinements as tests indicate. * Building out the test suite. * web: test the simple things. Fix what the tests revealed. - Move `EmptyState.test.ts` into the `./tests` folder. - Provide unit tests for: - Alert - Divider - Expand - Label - LoadingOverlay - Give all tested items an Interface and a functional variant for rendering - Give Label an alternative syntax for declaring alert levels - Remove the slot name in LoadingOverlay - Change the slot call in `./enterprise/rac/index.ts` to not need the slot name as well - Change the attribute names `topMost`, `textOpen`, and `textClosed` to `topmost`, `text-open`, and `text-closed`, respectively. - Change locations in the code where those are used to correspond ** Why interfaces: ** Provides another check on the input/output boundaries of our elements, gives Storybook and WebdriverIO another validation to check, and guarantees any rendering functions cannot be passed invalid property names. ** Why functions for rendering: ** Providing functions for rendering gets us one step closer to dynamically defining our forms-in-code at runtime without losing any type safety. ** Why rename the attributes: ** A *very* subtle bug: [Element:setAttribute()](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute) automatically "converts an attribute name to all lower-case when called on an HTML element in an HTML document." The three attributes renamed are all treated *as* attributes, either classic boolean or stringly-typed attributes, and attempting to manipulate them with `setAttribute()` will fail. All of these attributes are presentational; none of them end up in a transaction with the back-end, so kebab-to-camel conversions are not a concern. Also, ["topmost" is one word](https://www.merriam-webster.com/dictionary/topmost). ** Why remove the slot name: ** Because there was only one slot. A name is not needed. * Fix minor spelling error.
This commit is contained in:
@ -1,7 +1,10 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
|
||||
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
@ -13,36 +16,84 @@ export enum Level {
|
||||
Danger = "pf-m-danger",
|
||||
}
|
||||
|
||||
export const levelNames = ["warning", "info", "success", "danger"];
|
||||
export type Levels = (typeof levelNames)[number];
|
||||
|
||||
export interface IAlert {
|
||||
inline?: boolean;
|
||||
plain?: boolean;
|
||||
icon?: string;
|
||||
level?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Alert
|
||||
* @element ak-alert
|
||||
*
|
||||
* Alerts are in-page elements intended to draw the user's attention and alert them to important
|
||||
* details. Alerts are used alongside form elements to warn users of potential mistakes they can
|
||||
* make, as well as in in-line documentation.
|
||||
*/
|
||||
@customElement("ak-alert")
|
||||
export class Alert extends AKElement {
|
||||
export class Alert extends AKElement implements IAlert {
|
||||
/**
|
||||
* Whether or not to display the entire component's contents in-line or not.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
inline = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
plain = false;
|
||||
|
||||
/**
|
||||
* Method of determining severity
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property()
|
||||
level: Level = Level.Warning;
|
||||
level: Level | Levels = Level.Warning;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
/**
|
||||
* Icon to display
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property()
|
||||
icon = "fa-exclamation-circle";
|
||||
|
||||
static get styles() {
|
||||
return [PFBase, PFAlert];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div
|
||||
class="pf-c-alert ${this.inline ? "pf-m-inline" : ""} ${this.plain
|
||||
? "pf-m-plain"
|
||||
: ""} ${this.level}"
|
||||
>
|
||||
get classmap() {
|
||||
const level = levelNames.includes(this.level)
|
||||
? `pf-m-${this.level}`
|
||||
: (this.level as string);
|
||||
return {
|
||||
"pf-c-alert": true,
|
||||
"pf-m-inline": this.inline,
|
||||
"pf-m-plain": this.plain,
|
||||
[level]: true,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div class="${classMap(this.classmap)}">
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<i class="fas ${this.icon}"></i>
|
||||
</div>
|
||||
<h4 class="pf-c-alert__title">
|
||||
<slot></slot>
|
||||
</h4>
|
||||
<h4 class="pf-c-alert__title"><slot></slot></h4>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export function akAlert(properties: IAlert, content: SlottedTemplateResult = nothing) {
|
||||
const message = typeof content === "string" ? html`<span>${content}</span>` : content;
|
||||
return html`<ak-alert ${spread(properties as Spread)}>${message}</ak-alert>`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-alert": Alert;
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { type SlottedTemplateResult } from "@goauthentik/elements/types";
|
||||
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { css, html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@customElement("ak-divider")
|
||||
export class Divider extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
css`
|
||||
@ -35,11 +36,18 @@ export class Divider extends AKElement {
|
||||
];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="separator"><slot></slot></div>`;
|
||||
render() {
|
||||
return html`<div class="separator">
|
||||
<slot></slot>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export function akDivider(content: SlottedTemplateResult = nothing) {
|
||||
const message = typeof content === "string" ? html`<span>${content}</span>` : content;
|
||||
return html`<ak-divider>${message}</ak-divider>`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-divider": Divider;
|
||||
|
@ -1,17 +1,26 @@
|
||||
import { PFSize } from "@goauthentik/common/enums.js";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/Spinner";
|
||||
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
|
||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export interface IEmptyState {
|
||||
icon?: string;
|
||||
loading?: boolean;
|
||||
fullHeight?: boolean;
|
||||
header?: string;
|
||||
}
|
||||
|
||||
@customElement("ak-empty-state")
|
||||
export class EmptyState extends AKElement {
|
||||
export class EmptyState extends AKElement implements IEmptyState {
|
||||
@property({ type: String })
|
||||
icon = "";
|
||||
|
||||
@ -24,7 +33,7 @@ export class EmptyState extends AKElement {
|
||||
@property()
|
||||
header?: string;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
PFEmptyState,
|
||||
@ -38,7 +47,7 @@ export class EmptyState extends AKElement {
|
||||
];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
render() {
|
||||
return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}">
|
||||
<div class="pf-c-empty-state__content">
|
||||
${this.loading
|
||||
@ -64,6 +73,12 @@ export class EmptyState extends AKElement {
|
||||
}
|
||||
}
|
||||
|
||||
export function akEmptyState(properties: IEmptyState, content: SlottedTemplateResult = nothing) {
|
||||
const message =
|
||||
typeof content === "string" ? html`<span slot="body">${content}</span>` : content;
|
||||
return html`<ak-empty-state ${spread(properties as Spread)}>${message}</ak-empty-state>`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-empty-state": EmptyState;
|
||||
|
@ -1,24 +1,32 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export interface IExpand {
|
||||
expanded?: boolean;
|
||||
textOpen?: string;
|
||||
textClosed?: string;
|
||||
}
|
||||
|
||||
@customElement("ak-expand")
|
||||
export class Expand extends AKElement {
|
||||
export class Expand extends AKElement implements IExpand {
|
||||
@property({ type: Boolean })
|
||||
expanded = false;
|
||||
|
||||
@property()
|
||||
@property({ type: String, attribute: "text-open" })
|
||||
textOpen = msg("Show less");
|
||||
|
||||
@property()
|
||||
@property({ type: String, attribute: "text-closed" })
|
||||
textClosed = msg("Show more");
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
PFExpandableSection,
|
||||
@ -30,7 +38,7 @@ export class Expand extends AKElement {
|
||||
];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
render() {
|
||||
return html`<div
|
||||
class="pf-c-expandable-section pf-m-display-lg pf-m-indented ${this.expanded
|
||||
? "pf-m-expanded"
|
||||
@ -58,6 +66,11 @@ export class Expand extends AKElement {
|
||||
}
|
||||
}
|
||||
|
||||
export function akExpand(properties: IExpand, content: SlottedTemplateResult = nothing) {
|
||||
const message = typeof content === "string" ? html`<span>${content}</span>` : content;
|
||||
return html`<ak-expand ${spread(properties as Spread)}>${message}</ak-expand>`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-expand": Expand;
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
|
||||
import PFLabel from "@patternfly/patternfly/components/Label/label.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
@ -13,8 +16,25 @@ export enum PFColor {
|
||||
Grey = "",
|
||||
}
|
||||
|
||||
export const levelNames = ["warning", "info", "success", "danger"];
|
||||
export type Level = (typeof levelNames)[number];
|
||||
|
||||
type Chrome = [Level, PFColor, string, string];
|
||||
const chromeList: Chrome[] = [
|
||||
["danger", PFColor.Red, "pf-m-red", "fa-times"],
|
||||
["warning", PFColor.Orange, "pf-m-orange", "fa-exclamation-triangle"],
|
||||
["success", PFColor.Green, "pf-m-green", "fa-check"],
|
||||
["info", PFColor.Grey, "pf-m-grey", "fa-info-circle"],
|
||||
];
|
||||
|
||||
export interface ILabel {
|
||||
icon?: string;
|
||||
compact?: boolean;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
@customElement("ak-label")
|
||||
export class Label extends AKElement {
|
||||
export class Label extends AKElement implements ILabel {
|
||||
@property()
|
||||
color: PFColor = PFColor.Grey;
|
||||
|
||||
@ -24,33 +44,31 @@ export class Label extends AKElement {
|
||||
@property({ type: Boolean })
|
||||
compact = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
static get styles() {
|
||||
return [PFBase, PFLabel];
|
||||
}
|
||||
|
||||
getDefaultIcon(): string {
|
||||
switch (this.color) {
|
||||
case PFColor.Green:
|
||||
return "fa-check";
|
||||
case PFColor.Orange:
|
||||
return "fa-exclamation-triangle";
|
||||
case PFColor.Red:
|
||||
return "fa-times";
|
||||
case PFColor.Grey:
|
||||
return "fa-info-circle";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
get classesAndIcon() {
|
||||
const chrome = chromeList.find(
|
||||
([level, color]) => this.color === level || this.color === color,
|
||||
);
|
||||
const [illo, icon] = chrome ? chrome.slice(2) : ["pf-m-grey", "fa-info-circle"];
|
||||
return {
|
||||
classes: {
|
||||
"pf-c-label": true,
|
||||
"pf-m-compact": this.compact,
|
||||
...(illo ? { [illo]: true } : {}),
|
||||
},
|
||||
icon: this.icon ? this.icon : icon,
|
||||
};
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<span class="pf-c-label ${this.color} ${this.compact ? "pf-m-compact" : ""}">
|
||||
render() {
|
||||
const { classes, icon } = this.classesAndIcon;
|
||||
return html`<span class=${classMap(classes)}>
|
||||
<span class="pf-c-label__content">
|
||||
<span class="pf-c-label__icon">
|
||||
<i
|
||||
class="fas fa-fw ${this.icon || this.getDefaultIcon()}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i class="fas fa-fw ${icon}" aria-hidden="true"></i>
|
||||
</span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
@ -58,6 +76,11 @@ export class Label extends AKElement {
|
||||
}
|
||||
}
|
||||
|
||||
export function akLabel(properties: ILabel, content: SlottedTemplateResult = nothing) {
|
||||
const message = typeof content === "string" ? html`<span>${content}</span>` : content;
|
||||
return html`<ak-label ${spread(properties as Spread)}>${message}</ak-label>`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-label": Label;
|
||||
|
@ -1,17 +1,24 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@customElement("ak-loading-overlay")
|
||||
export class LoadingOverlay extends AKElement {
|
||||
@property({ type: Boolean })
|
||||
topMost = false;
|
||||
interface ILoadingOverlay {
|
||||
topmost?: boolean;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
@customElement("ak-loading-overlay")
|
||||
export class LoadingOverlay extends AKElement implements ILoadingOverlay {
|
||||
// Do not camelize: https://www.merriam-webster.com/dictionary/topmost
|
||||
@property({ type: Boolean, attribute: "topmost" })
|
||||
topmost = false;
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
css`
|
||||
@ -25,20 +32,30 @@ export class LoadingOverlay extends AKElement {
|
||||
background-color: var(--pf-global--BackgroundColor--dark-transparent-200);
|
||||
z-index: 1;
|
||||
}
|
||||
:host([topMost]) {
|
||||
:host([topmost]) {
|
||||
z-index: 999;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
render() {
|
||||
return html`<ak-empty-state loading header="">
|
||||
<slot name="body" slot="body"></slot>
|
||||
<slot></slot>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
}
|
||||
|
||||
export function akLoadingOverlay(
|
||||
properties: ILoadingOverlay,
|
||||
content: SlottedTemplateResult = nothing,
|
||||
) {
|
||||
const message = typeof content === "string" ? html`<span>${content}</span>` : content;
|
||||
return html`<ak-loading-overlay ${spread(properties as Spread)}
|
||||
>${message}</ak-loading-overlay
|
||||
>`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-loading-overlay": LoadingOverlay;
|
||||
|
@ -56,7 +56,7 @@ export class ModalForm extends ModalButton {
|
||||
|
||||
renderModalInner(): TemplateResult {
|
||||
return html`${this.loading
|
||||
? html`<ak-loading-overlay ?topMost=${true}></ak-loading-overlay>`
|
||||
? html`<ak-loading-overlay topmost></ak-loading-overlay>`
|
||||
: html``}
|
||||
<section class="pf-c-modal-box__header pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
|
37
web/src/elements/tests/Alert.test.ts
Normal file
37
web/src/elements/tests/Alert.test.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { render } from "@goauthentik/elements/tests/utils.js";
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import "../Alert.js";
|
||||
import { Level, akAlert } from "../Alert.js";
|
||||
|
||||
describe("ak-alert", () => {
|
||||
it("should render an alert with the enum", async () => {
|
||||
render(html`<ak-alert level=${Level.Info}>This is an alert</ak-alert>`, document.body);
|
||||
|
||||
await expect(await $("ak-alert").$("div")).not.toHaveElementClass("pf-m-inline");
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-c-alert");
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-info");
|
||||
await expect(await $("ak-alert").$(".pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
|
||||
it("should render an alert with the attribute", async () => {
|
||||
render(html`<ak-alert level="info">This is an alert</ak-alert>`, document.body);
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-info");
|
||||
await expect(await $("ak-alert").$(".pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
|
||||
it("should render an alert with an inline class and the default level", async () => {
|
||||
render(html`<ak-alert inline>This is an alert</ak-alert>`, document.body);
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-warning");
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-inline");
|
||||
await expect(await $("ak-alert").$(".pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
|
||||
it("should render an alert as a function call", async () => {
|
||||
render(akAlert({ level: "info" }, "This is an alert"));
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-info");
|
||||
await expect(await $("ak-alert").$(".pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
});
|
35
web/src/elements/tests/Divider.test.ts
Normal file
35
web/src/elements/tests/Divider.test.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { render } from "@goauthentik/elements/tests/utils.js";
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import "../Divider.js";
|
||||
import { akDivider } from "../Divider.js";
|
||||
|
||||
describe("ak-divider", () => {
|
||||
it("should render the divider", async () => {
|
||||
render(html`<ak-divider></ak-divider>`);
|
||||
const empty = await $("ak-divider");
|
||||
await expect(empty).toExist();
|
||||
});
|
||||
|
||||
it("should render the divider with the specified text", async () => {
|
||||
render(html`<ak-divider><span>Your Message Here</span></ak-divider>`);
|
||||
const span = await $("ak-divider").$("span");
|
||||
await expect(span).toExist();
|
||||
await expect(span).toHaveText("Your Message Here");
|
||||
});
|
||||
|
||||
it("should render the divider as a function with the specified text", async () => {
|
||||
render(akDivider("Your Message As A Function"));
|
||||
const divider = await $("ak-divider");
|
||||
await expect(divider).toExist();
|
||||
await expect(divider).toHaveText("Your Message As A Function");
|
||||
});
|
||||
|
||||
it("should render the divider as a function", async () => {
|
||||
render(akDivider());
|
||||
const empty = await $("ak-divider");
|
||||
await expect(empty).toExist();
|
||||
});
|
||||
});
|
@ -1,12 +1,23 @@
|
||||
import { render } from "@goauthentik/elements/tests/utils.js";
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
|
||||
import "./EmptyState.js";
|
||||
import { render } from "./tests/utils.js";
|
||||
import "../EmptyState.js";
|
||||
import { akEmptyState } from "../EmptyState.js";
|
||||
|
||||
describe("ak-empty-state", () => {
|
||||
afterEach(async () => {
|
||||
await browser.execute(async () => {
|
||||
await document.body.querySelector("ak-empty-state")?.remove();
|
||||
if (document.body["_$litPart$"]) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
await delete document.body["_$litPart$"];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the default loader", async () => {
|
||||
render(html`<ak-empty-state ?loading=${true} header=${msg("Loading")}> </ak-empty-state>`);
|
||||
|
||||
@ -48,4 +59,33 @@ describe("ak-empty-state", () => {
|
||||
const message = await $("ak-empty-state").$(">>>.pf-c-empty-state__body").$(">>>p");
|
||||
await expect(message).toHaveText("Try again with a different filter");
|
||||
});
|
||||
|
||||
it("should render as a function call", async () => {
|
||||
render(akEmptyState({ loading: true }, "Being Thoughtful"));
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
|
||||
const header = await $("ak-empty-state").$(">>>.pf-c-empty-state__body");
|
||||
await expect(header).toHaveText("Being Thoughtful");
|
||||
});
|
||||
|
||||
it("should render as a complex function call", async () => {
|
||||
render(
|
||||
akEmptyState(
|
||||
{ loading: true },
|
||||
html` <span slot="body">Introspecting</span>
|
||||
<span slot="primary">... carefully</span>`,
|
||||
),
|
||||
);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
|
||||
const header = await $("ak-empty-state").$(">>>.pf-c-empty-state__body");
|
||||
await expect(header).toHaveText("Introspecting");
|
||||
|
||||
const primary = await $("ak-empty-state").$(">>>.pf-c-empty-state__primary");
|
||||
await expect(primary).toHaveText("... carefully");
|
||||
});
|
||||
});
|
93
web/src/elements/tests/Expand.test.ts
Normal file
93
web/src/elements/tests/Expand.test.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { render } from "@goauthentik/elements/tests/utils.js";
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import "../Expand.js";
|
||||
import { akExpand } from "../Expand.js";
|
||||
|
||||
describe("ak-expand", () => {
|
||||
afterEach(async () => {
|
||||
await browser.execute(async () => {
|
||||
await document.body.querySelector("ak-expand")?.remove();
|
||||
if (document.body["_$litPart$"]) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
await delete document.body["_$litPart$"];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the expansion content hidden by default", async () => {
|
||||
render(html`<ak-expand><p>This is the expanded text</p></ak-expand>`);
|
||||
const text = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
|
||||
await expect(text).not.toBeDisplayed();
|
||||
});
|
||||
|
||||
it("should render the expansion content visible on demand", async () => {
|
||||
render(html`<ak-expand expanded><p>This is the expanded text</p></ak-expand>`);
|
||||
const paragraph = await $("ak-expand").$(">>>p");
|
||||
await expect(paragraph).toExist();
|
||||
await expect(paragraph).toBeDisplayed();
|
||||
await expect(paragraph).toHaveText("This is the expanded text");
|
||||
});
|
||||
|
||||
it("should respond to the click event", async () => {
|
||||
render(html`<ak-expand><p>This is the expanded text</p></ak-expand>`);
|
||||
let content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
|
||||
await expect(content).toExist();
|
||||
await expect(content).not.toBeDisplayed();
|
||||
const control = await $("ak-expand").$(">>>button");
|
||||
|
||||
await control.click();
|
||||
content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
|
||||
await expect(content).toExist();
|
||||
await expect(content).toBeDisplayed();
|
||||
|
||||
await control.click();
|
||||
content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
|
||||
await expect(content).toExist();
|
||||
await expect(content).not.toBeDisplayed();
|
||||
});
|
||||
|
||||
it("should honor the header properties", async () => {
|
||||
render(
|
||||
html`<ak-expand text-open="Close it" text-closed="Open it" expanded
|
||||
><p>This is the expanded text</p></ak-expand
|
||||
>`,
|
||||
);
|
||||
const paragraph = await $("ak-expand").$(">>>p");
|
||||
await expect(paragraph).toExist();
|
||||
await expect(paragraph).toBeDisplayed();
|
||||
await expect(paragraph).toHaveText("This is the expanded text");
|
||||
await expect(await $("ak-expand").$(".pf-c-expandable-section__toggle-text")).toHaveText(
|
||||
"Close it",
|
||||
);
|
||||
|
||||
const control = await $("ak-expand").$(">>>button");
|
||||
await control.click();
|
||||
await expect(await $("ak-expand").$(".pf-c-expandable-section__toggle-text")).toHaveText(
|
||||
"Open it",
|
||||
);
|
||||
});
|
||||
|
||||
it("should honor the header properties via a function call", async () => {
|
||||
render(
|
||||
akExpand(
|
||||
{ "expanded": true, "text-open": "Close it now", "text-closed": "Open it now" },
|
||||
html`<p>This is the new text.</p>`,
|
||||
),
|
||||
);
|
||||
const paragraph = await $("ak-expand").$(">>>p");
|
||||
await expect(paragraph).toExist();
|
||||
await expect(paragraph).toBeDisplayed();
|
||||
await expect(paragraph).toHaveText("This is the new text.");
|
||||
await expect(await $("ak-expand").$(".pf-c-expandable-section__toggle-text")).toHaveText(
|
||||
"Close it now",
|
||||
);
|
||||
const control = await $("ak-expand").$(">>>button");
|
||||
await control.click();
|
||||
await expect(await $("ak-expand").$(".pf-c-expandable-section__toggle-text")).toHaveText(
|
||||
"Open it now",
|
||||
);
|
||||
});
|
||||
});
|
62
web/src/elements/tests/Label.test.ts
Normal file
62
web/src/elements/tests/Label.test.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { render } from "@goauthentik/elements/tests/utils.js";
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import "../Label.js";
|
||||
import { PFColor, akLabel } from "../Label.js";
|
||||
|
||||
describe("ak-label", () => {
|
||||
it("should render a label with the enum", async () => {
|
||||
render(html`<ak-label color=${PFColor.Red}>This is a label</ak-label>`);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-c-label");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).not.toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-red");
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-times");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a label with the attribute", async () => {
|
||||
render(html`<ak-label color="success">This is a label</ak-label>`);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-green");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a compact label with the default level", async () => {
|
||||
render(html`<ak-label compact>This is a label</ak-label>`);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-grey");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-info-circle");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a compact label with an icon and the default level", async () => {
|
||||
render(html`<ak-label compact icon="fa-coffee">This is a label</ak-label>`);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-grey");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-coffee");
|
||||
});
|
||||
|
||||
it("should render a label with the function", async () => {
|
||||
render(akLabel({ color: "success" }, "This is a label"));
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-green");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
});
|
33
web/src/elements/tests/LoadingOverlay.test.ts
Normal file
33
web/src/elements/tests/LoadingOverlay.test.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { render } from "@goauthentik/elements/tests/utils.js";
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import "../LoadingOverlay.js";
|
||||
import { akLoadingOverlay } from "../LoadingOverlay.js";
|
||||
|
||||
describe("ak-loading-overlay", () => {
|
||||
it("should render the default loader", async () => {
|
||||
render(html`<ak-loading-overlay></ak-loading-overlay>`);
|
||||
|
||||
const empty = await $("ak-loading-overlay");
|
||||
await expect(empty).toExist();
|
||||
});
|
||||
|
||||
it("should render a slotted message", async () => {
|
||||
render(
|
||||
html`<ak-loading-overlay>
|
||||
<p>Try again with a different filter</p>
|
||||
</ak-loading-overlay>`,
|
||||
);
|
||||
|
||||
const message = await $("ak-loading-overlay").$(">>>p");
|
||||
await expect(message).toHaveText("Try again with a different filter");
|
||||
});
|
||||
|
||||
it("as a function should render a slotted message", async () => {
|
||||
render(akLoadingOverlay({}, "Try again with another filter"));
|
||||
const overlay = await $("ak-loading-overlay");
|
||||
await expect(overlay).toHaveText("Try again with another filter");
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
|
||||
import { TemplateResult } from "lit";
|
||||
import { TemplateResult, nothing } from "lit";
|
||||
import { ReactiveControllerHost } from "lit";
|
||||
|
||||
export type ReactiveElementHost<T = AKElement> = Partial<ReactiveControllerHost> & T;
|
||||
@ -73,3 +73,6 @@ export type SelectGrouped<T = never> = {
|
||||
*/
|
||||
export type GroupedOptions<T = never> = SelectGrouped<T> | SelectFlat<T>;
|
||||
export type SelectOptions<T = never> = SelectOption<T>[] | GroupedOptions<T>;
|
||||
|
||||
export type SlottedTemplateResult = string | TemplateResult | typeof nothing;
|
||||
export type Spread = { [key: string]: unknown };
|
||||
|
@ -316,7 +316,7 @@ export class RacInterface extends Interface {
|
||||
${this.clientState !== GuacClientState.CONNECTED
|
||||
? html`
|
||||
<ak-loading-overlay>
|
||||
<span slot="body">
|
||||
<span>
|
||||
${this.hasConnected
|
||||
? html`${this.reconnectingMessage}`
|
||||
: html`${msg("Connecting...")}`}
|
||||
|
@ -68,7 +68,7 @@ export class LibraryApplication extends AKElement {
|
||||
renderExpansion(application: Application) {
|
||||
const me = rootInterface<UserInterface>()?.me;
|
||||
|
||||
return html`<ak-expand textOpen=${msg("Less details")} textClosed=${msg("More details")}>
|
||||
return html`<ak-expand text-open=${msg("Less details")} text-closed=${msg("More details")}>
|
||||
<div class="pf-c-content">
|
||||
<small>${application.metaPublisher}</small>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user