Skip to content

Commit

Permalink
web/admin/better-footer-links
Browse files Browse the repository at this point in the history
# What

- Remove the "JSON or YAML" language from the AdminSettings page for describing FooterLinks inputs.
- Add unit tests for ArrayInput and AdminSettingsFooterLinks.
- Provide a property for accessing a component's value

# Why

Providing a property by which the JSONified version of the value can be accessed enhances the
ability of tests to independently check that the value is in a state we desire, since properties can
easily be accessed across the wire protocol used by browser-based testing environments.
  • Loading branch information
kensternberg-authentik committed Nov 13, 2024
1 parent afcf0e7 commit 5d874cb
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 6 deletions.
9 changes: 5 additions & 4 deletions web/src/admin/admin-settings/AdminSettingsFooterLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export interface IFooterLinkInput {
footerLink: FooterLink;
}

const LEGAL_SCHEMES = ["http://", "https://", "mailto:"];
const hasLegalScheme = (url: string) =>
LEGAL_SCHEMES.some((scheme) => url.substr(0, scheme.length).toLowerCase() === scheme);

@customElement("ak-admin-settings-footer-link")
export class FooterLinkInput extends AkControlElement<FooterLink> {
static get styles() {
Expand Down Expand Up @@ -50,10 +54,7 @@ export class FooterLinkInput extends AkControlElement<FooterLink> {

get isValid() {
const href = this.json()?.href ?? "";
return (
(href.substr(0, 7) === "http://" || href.substr(0, 8) === "https://") &&
URL.canParse(href)
);
return hasLegalScheme(href) && URL.canParse(href);
}

render() {
Expand Down
3 changes: 1 addition & 2 deletions web/src/admin/admin-settings/AdminSettingsForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,8 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
</ak-array-input>
<p class="pf-c-form__helper-text">
${msg(
"This option configures the footer links on the flow executor pages. It must be a valid YAML or JSON list and can be used as follows:",
"This option configures the footer links on the flow executor pages. The URL is limited to web and mail addresses. If the name is left blank, the URL will be shown.",
)}
<code>[{"name": "Link Name","href":"https://goauthentik.io"}]</code>
</p>
</ak-form-element-horizontal>
<ak-switch-input
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { render } from "@goauthentik/elements/tests/utils.js";
import { $, expect } from "@wdio/globals";

import { html } from "lit";

import "../AdminSettingsFooterLinks.js";

describe("ak-admin-settings-footer-link", () => {
afterEach(async () => {
await browser.execute(async () => {
await document.body.querySelector("ak-admin-settings-footer-link")?.remove();
if (document.body["_$litPart$"]) {
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
await delete document.body["_$litPart$"];
}
});
});

it("should render an empty control", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = await $("ak-admin-settings-footer-link");
await expect(await link.getProperty("isValid")).toStrictEqual(false);
await expect(await link.getProperty("toJson")).toEqual({ name: "", href: "" });
});

it("should not be valid if just a name is filled in", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = await $("ak-admin-settings-footer-link");
await link.$('input[name="name"]').setValue("foo");
await expect(await link.getProperty("isValid")).toStrictEqual(false);
await expect(await link.getProperty("toJson")).toEqual({ name: "foo", href: "" });
});

it("should be valid if just a URL is filled in", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = await $("ak-admin-settings-footer-link");
await link.$('input[name="href"]').setValue("https://foo.com");
await expect(await link.getProperty("isValid")).toStrictEqual(true);
await expect(await link.getProperty("toJson")).toEqual({
name: "",
href: "https://foo.com",
});
});

it("should be valid if both are filled in", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = await $("ak-admin-settings-footer-link");
await link.$('input[name="name"]').setValue("foo");
await link.$('input[name="href"]').setValue("https://foo.com");
await expect(await link.getProperty("isValid")).toStrictEqual(true);
await expect(await link.getProperty("toJson")).toEqual({
name: "foo",
href: "https://foo.com",
});
});

it("should not be valid if the URL is not valid", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = await $("ak-admin-settings-footer-link");
await link.$('input[name="name"]').setValue("foo");
await link.$('input[name="href"]').setValue("never://foo.com");
await expect(await link.getProperty("toJson")).toEqual({
name: "foo",
href: "never://foo.com",
});
await expect(await link.getProperty("isValid")).toStrictEqual(false);
});
});
4 changes: 4 additions & 0 deletions web/src/elements/AkControlElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export class AkControlElement<T = string | string[]> extends AKElement {
throw new Error("Controllers using this protocol must override this method");
}

get toJson(): T {
return this.json();
}

get isValid(): boolean {
return true;
}
Expand Down
55 changes: 55 additions & 0 deletions web/src/elements/tests/ak-array-input.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import "@goauthentik/admin/admin-settings/AdminSettingsFooterLinks.js";
import { render } from "@goauthentik/elements/tests/utils.js";
import { $, expect } from "@wdio/globals";

import { html } from "lit";

import { FooterLink } from "@goauthentik/api";

import "../ak-array-input.js";

const sampleItems: FooterLink[] = [
{ name: "authentik", href: "https://goauthentik.io" },
{ name: "authentik docs", href: "https://docs.goauthentik.io/docs/" },
];

describe("ak-array-input", () => {
afterEach(async () => {
await browser.execute(async () => {
await document.body.querySelector("ak-array-input")?.remove();
if (document.body["_$litPart$"]) {
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
await delete document.body["_$litPart$"];
}
});
});

const component = (items: FooterLink[] = []) =>
render(
html` <ak-array-input
id="ak-array-input"
.items=${items}
.newItem=${() => ({ name: "", href: "" })}
.row=${(f?: FooterLink) =>
html`<ak-admin-settings-footer-link name="footerLink" .footerLink=${f}>
</ak-admin-settings-footer-link>`}
validate
></ak-array-input>`,
);

it("should render an empty control", async () => {
await component();
const link = await $("ak-array-input");
await browser.pause(500);
await expect(await link.getProperty("isValid")).toStrictEqual(true);
await expect(await link.getProperty("toJson")).toEqual([]);
});

it("should render a populated component", async () => {
await component(sampleItems);
const link = await $("ak-array-input");
await browser.pause(500);
await expect(await link.getProperty("isValid")).toStrictEqual(true);
await expect(await link.getProperty("toJson")).toEqual(sampleItems);
});
});

0 comments on commit 5d874cb

Please sign in to comment.