Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

web: unit tests for the simple things, with fixes that the tests revealed #11633

Merged
merged 4 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 65 additions & 14 deletions web/src/elements/Alert.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 | Levels = Level.Warning;

/**
* Icon to display
*
* @attr
*/
@property()
level: Level = Level.Warning;
icon = "fa-exclamation-circle";

static get styles(): CSSResult[] {
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;
Expand Down
16 changes: 12 additions & 4 deletions web/src/elements/Divider.ts
Original file line number Diff line number Diff line change
@@ -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`
Expand Down Expand Up @@ -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;
Expand Down
23 changes: 19 additions & 4 deletions web/src/elements/EmptyState.ts
Original file line number Diff line number Diff line change
@@ -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 = "";

Expand All @@ -24,7 +33,7 @@ export class EmptyState extends AKElement {
@property()
header?: string;

static get styles(): CSSResult[] {
static get styles() {
return [
PFBase,
PFEmptyState,
Expand All @@ -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
Expand All @@ -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;
Expand Down
25 changes: 19 additions & 6 deletions web/src/elements/Expand.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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"
Expand Down Expand Up @@ -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;
Expand Down
67 changes: 45 additions & 22 deletions web/src/elements/Label.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;

Expand All @@ -24,40 +44,43 @@ 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>
</span>`;
}
}

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;
Expand Down
Loading
Loading