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 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;
|
||||
|
||||
Reference in New Issue
Block a user