diff --git a/web/src/elements/Alert.ts b/web/src/elements/Alert.ts index 12b9e0fc84..f27e510ed8 100644 --- a/web/src/elements/Alert.ts +++ b/web/src/elements/Alert.ts @@ -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`
+ 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`
- +
-

- -

+

`; } } +export function akAlert(properties: IAlert, content: SlottedTemplateResult = nothing) { + const message = typeof content === "string" ? html`${content}` : content; + return html`${message}`; +} + declare global { interface HTMLElementTagNameMap { "ak-alert": Alert; diff --git a/web/src/elements/Divider.ts b/web/src/elements/Divider.ts index a6a0ae5887..ed847bedaa 100644 --- a/web/src/elements/Divider.ts +++ b/web/src/elements/Divider.ts @@ -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`
`; + render() { + return html`
+ +
`; } } +export function akDivider(content: SlottedTemplateResult = nothing) { + const message = typeof content === "string" ? html`${content}` : content; + return html`${message}`; +} + declare global { interface HTMLElementTagNameMap { "ak-divider": Divider; diff --git a/web/src/elements/EmptyState.ts b/web/src/elements/EmptyState.ts index d2000d25a1..42d92c378d 100644 --- a/web/src/elements/EmptyState.ts +++ b/web/src/elements/EmptyState.ts @@ -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`
${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`${content}` : content; + return html`${message}`; +} + declare global { interface HTMLElementTagNameMap { "ak-empty-state": EmptyState; diff --git a/web/src/elements/Expand.ts b/web/src/elements/Expand.ts index f081b96f42..6d5538bdc9 100644 --- a/web/src/elements/Expand.ts +++ b/web/src/elements/Expand.ts @@ -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`
${content}` : content; + return html`${message}`; +} + declare global { interface HTMLElementTagNameMap { "ak-expand": Expand; diff --git a/web/src/elements/Label.ts b/web/src/elements/Label.ts index c90e420320..8aeffa8398 100644 --- a/web/src/elements/Label.ts +++ b/web/src/elements/Label.ts @@ -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` + render() { + const { classes, icon } = this.classesAndIcon; + return html` - + @@ -58,6 +76,11 @@ export class Label extends AKElement { } } +export function akLabel(properties: ILabel, content: SlottedTemplateResult = nothing) { + const message = typeof content === "string" ? html`${content}` : content; + return html`${message}`; +} + declare global { interface HTMLElementTagNameMap { "ak-label": Label; diff --git a/web/src/elements/LoadingOverlay.ts b/web/src/elements/LoadingOverlay.ts index 1165609ead..de8e198d6e 100644 --- a/web/src/elements/LoadingOverlay.ts +++ b/web/src/elements/LoadingOverlay.ts @@ -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` - + `; } } +export function akLoadingOverlay( + properties: ILoadingOverlay, + content: SlottedTemplateResult = nothing, +) { + const message = typeof content === "string" ? html`${content}` : content; + return html`${message}`; +} + declare global { interface HTMLElementTagNameMap { "ak-loading-overlay": LoadingOverlay; diff --git a/web/src/elements/forms/ModalForm.ts b/web/src/elements/forms/ModalForm.ts index c6f2727e89..9cecb1fa0e 100644 --- a/web/src/elements/forms/ModalForm.ts +++ b/web/src/elements/forms/ModalForm.ts @@ -56,7 +56,7 @@ export class ModalForm extends ModalButton { renderModalInner(): TemplateResult { return html`${this.loading - ? html`` + ? html`` : html``}
diff --git a/web/src/elements/tests/Alert.test.ts b/web/src/elements/tests/Alert.test.ts new file mode 100644 index 0000000000..db6fba72f9 --- /dev/null +++ b/web/src/elements/tests/Alert.test.ts @@ -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`This is an 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`This is an 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`This is an 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"); + }); +}); diff --git a/web/src/elements/tests/Divider.test.ts b/web/src/elements/tests/Divider.test.ts new file mode 100644 index 0000000000..e431672537 --- /dev/null +++ b/web/src/elements/tests/Divider.test.ts @@ -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``); + const empty = await $("ak-divider"); + await expect(empty).toExist(); + }); + + it("should render the divider with the specified text", async () => { + render(html`Your Message Here`); + 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(); + }); +}); diff --git a/web/src/elements/EmptyState.test.ts b/web/src/elements/tests/EmptyState.test.ts similarity index 53% rename from web/src/elements/EmptyState.test.ts rename to web/src/elements/tests/EmptyState.test.ts index 4ebe3ceccd..265b1f91e3 100644 --- a/web/src/elements/EmptyState.test.ts +++ b/web/src/elements/tests/EmptyState.test.ts @@ -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` `); @@ -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` Introspecting + ... carefully`, + ), + ); + + 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"); + }); }); diff --git a/web/src/elements/tests/Expand.test.ts b/web/src/elements/tests/Expand.test.ts new file mode 100644 index 0000000000..bc878b2cb8 --- /dev/null +++ b/web/src/elements/tests/Expand.test.ts @@ -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`

This is the expanded text

`); + 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`

This is the expanded text

`); + 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`

This is the expanded text

`); + 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`

This is the expanded text

`, + ); + 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`

This is the new text.

`, + ), + ); + 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", + ); + }); +}); diff --git a/web/src/elements/tests/Label.test.ts b/web/src/elements/tests/Label.test.ts new file mode 100644 index 0000000000..97e3ff4b82 --- /dev/null +++ b/web/src/elements/tests/Label.test.ts @@ -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`This is a 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`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", + ); + }); + + it("should render a compact label with the default level", async () => { + render(html`This is a 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`This is a 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", + ); + }); +}); diff --git a/web/src/elements/tests/LoadingOverlay.test.ts b/web/src/elements/tests/LoadingOverlay.test.ts new file mode 100644 index 0000000000..35bbfa878f --- /dev/null +++ b/web/src/elements/tests/LoadingOverlay.test.ts @@ -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``); + + const empty = await $("ak-loading-overlay"); + await expect(empty).toExist(); + }); + + it("should render a slotted message", async () => { + render( + html` +

Try again with a different filter

+
`, + ); + + 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"); + }); +}); diff --git a/web/src/elements/types.ts b/web/src/elements/types.ts index 76f8cb231d..141e596846 100644 --- a/web/src/elements/types.ts +++ b/web/src/elements/types.ts @@ -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 = Partial & T; @@ -73,3 +73,6 @@ export type SelectGrouped = { */ export type GroupedOptions = SelectGrouped | SelectFlat; export type SelectOptions = SelectOption[] | GroupedOptions; + +export type SlottedTemplateResult = string | TemplateResult | typeof nothing; +export type Spread = { [key: string]: unknown }; diff --git a/web/src/enterprise/rac/index.ts b/web/src/enterprise/rac/index.ts index d90e98d6e0..7234257ca3 100644 --- a/web/src/enterprise/rac/index.ts +++ b/web/src/enterprise/rac/index.ts @@ -316,7 +316,7 @@ export class RacInterface extends Interface { ${this.clientState !== GuacClientState.CONNECTED ? html` - + ${this.hasConnected ? html`${this.reconnectingMessage}` : html`${msg("Connecting...")}`} diff --git a/web/src/user/LibraryApplication/index.ts b/web/src/user/LibraryApplication/index.ts index 85f5ccad4d..6c4ec6623f 100644 --- a/web/src/user/LibraryApplication/index.ts +++ b/web/src/user/LibraryApplication/index.ts @@ -68,7 +68,7 @@ export class LibraryApplication extends AKElement { renderExpansion(application: Application) { const me = rootInterface()?.me; - return html` + return html`
${application.metaPublisher}