@domx/dataelement
Version:
A DataElement base class for handling data state changes
268 lines (231 loc) • 10.7 kB
text/typescript
import { describe, it, expect } from "@jest/globals";
import { fixture, html} from "@domx/testutils/fixture";
import { RootState } from "../RootState";
import {
DataElement,
DataElementCtor
} from "../DataElement";
import {customDataElement, dataProperty} from "../decorators";
describe("DataElement", () => {
describe("data properties", () => {
it("can define an element", () => {
const el = fixture(html`<test-data-element></test-data-element>`);
expect(el.tagName).toBe("TEST-DATA-ELEMENT");
el.restore();
});
it("adds the __elementName", () => {
const el = fixture(html`<test-data-element></test-data-element>`);
expect((el.constructor as DataElementCtor).__elementName).toBe("test-data-element");
el.restore();
});
it("expands the data properties", () => {
const el = fixture<DataElement>(html`<test-data-element></test-data-element>`);
expect(el.__dataPropertyMetaData["state"].statePath).toBe("test-data-element.state");
el.restore();
});
it("expands the data properties with a stateId", () => {
const el = fixture<DataElement>(html`<test-instance-data-element user-id="1234"></test-instance-data-element>`);
expect(el.__dataPropertyMetaData["user"].statePath).toBe("test-instance-data-element.1234.user");
el.restore();
});
});
describe("state initialization", () => {
it("adds initial state to root state", () => {
const el = fixture(html`<test-data-element></test-data-element>`);
const state = RootState.get("test-data-element.state");
expect(state).toStrictEqual({status: "default"});
el.restore();
});
it("pulls state if already exists", () => {
const state = {status:"pulled from root"};
RootState.set("test-data-element.state", state);
const el = fixture<TestDataElement>(html`<test-data-element></test-data-element>`);
expect(el.state).toEqual(state);
el.restore();
});
it("removes state after being removed from the DOM", () => {
const el = fixture<TestDataElement>(html`<test-data-element></test-data-element>`);
expect(el.state).toStrictEqual({status: "default"});
const state = RootState.get("test-data-element.state");
expect(state).toStrictEqual({status: "default"});
el.restore();
expect(RootState.get("test-data-element.state")).toBe(null);
});
it("removes state after multiple DOM insertions", () => {
const el = fixture<TestDataElement>(html`<test-data-element></test-data-element>`);
expect(el.state).toStrictEqual({status: "default"});
expect(RootState.get("test-data-element.state")).toStrictEqual({status: "default"});
const el2 = fixture<TestDataElement>(html`<test-data-element></test-data-element>`);
expect(RootState.get("test-data-element.state")).toStrictEqual({status: "default"});
el.restore();
expect(RootState.get("test-data-element.state")).toStrictEqual({status: "default"});
el2.restore();
expect(RootState.get("test-data-element.state")).toBe(null);
});
});
describe("state changes", () => {
it("updates root state after changes occur", () => {
const el = fixture<TestDataElement>(html`<test-data-element></test-data-element>`);
expect(el.state).toStrictEqual({status: "default"});
expect(RootState.get("test-data-element.state")).toStrictEqual({status: "default"});
el.state.status = "updated";
el.dispatchEvent(new CustomEvent("state-changed"));
expect(RootState.get("test-data-element.state")).toStrictEqual({status: "updated"});
el.restore();
});
it("updates root state after changes occur", () => {
const el = fixture<TestDataElement>(html`<test-data-element></test-data-element>`);
const el2 = fixture<TestDataElement>(html`<test-data-element></test-data-element>`);
expect(el.state).toStrictEqual({status: "default"});
expect(RootState.get("test-data-element.state")).toStrictEqual({status: "default"});
el.state.status = "updated";
el.dispatchEvent(new CustomEvent("state-changed"));
expect(el2.state).toStrictEqual({status: "updated"});
el.restore();
el2.restore();
});
});
describe("stateId", () => {
it("uses the stateId", () => {
const el = fixture<TestDataElement>(html`
<test-instance-data-element user-id="1234"></test-instance-data-element>
`);
expect(RootState.get("test-instance-data-element.1234.user"))
.toStrictEqual({userName: "unknown"});
el.restore();
});
});
describe("decorators", () => {
it("supports a dataProperty decorator", () => {
const el = fixture<TestDecorators>(html`<test-decorators></test-decorators>`);
const ctor = el.constructor as DataElementCtor;
expect(ctor.dataProperties.user.changeEvent).toBe("user-changed");
expect(ctor.dataProperties.data.changeEvent).toBe("data-did-change");
el.restore();
});
it("supports setting the stateIdProperty", () => {
const el = fixture<TestDecorators>(html`<test-decorators></test-decorators>`);
const ctor = el.constructor as DataElementCtor;
expect(el.__dataPropertyMetaData.user.statePath).toBe("test-decorators.1234.user");
el.restore();
});
});
describe("stateId change; refreshState", () => {
it("changes the state tree appropriately", () => {
const el = fixture<TestInstanceDataElement>(html`
<test-instance-data-element user-id="1234"></test-instance-data-element>
`);
expect(RootState.get("test-instance-data-element.1234.user"))
.toStrictEqual({userName: "unknown"});
el.testChange();
expect(RootState.get("test-instance-data-element.1234.user"))
.toStrictEqual({userName: "joeuser"});
el.setAttribute("user-id", "9876");
expect(RootState.get("test-instance-data-element.1234.user"))
.toBe(null)
expect(RootState.get("test-instance-data-element.9876.user"))
.toStrictEqual({userName: "unknown"});
});
});
describe("dispatchChange", () => {
it("dispatches state-change by default", () => {
const el = fixture<TestDataElement>(html`<test-data-element></test-data-element>`)
let message = "";
el.addEventListener("state-changed", (event: Event) => {
message = "event dispatched";
});
el.testDispatch();
expect(message).toBe("event dispatched");
});
it("dispatches the correct change event if configured", () => {
const el = fixture<TestInstanceDataElement>(html`<test-instance-data-element></test-instance-data-element>`)
let message = "";
el.addEventListener("user-changed", (event: Event) => {
message = "event dispatched";
});
el.testDispatch();
expect(message).toBe("event dispatched");
});
it("dispatches change with data", () => {
const el = fixture<TestInstanceDataElement>(html`<test-instance-data-element></test-instance-data-element>`)
let message = "";
el.addEventListener("user-changed", (event: Event) => {
message = "event dispatched";
});
el.testDispatchWithData();
expect(message).toBe("event dispatched");
});
it("does not dispatch change if the data is the same", () => {
const el = fixture<TestInstanceDataElement>(html`<test-instance-data-element></test-instance-data-element>`)
let message = "";
el.addEventListener("user-changed", (event: Event) => {
message = "event dispatched";
});
el.testDispatch(); // rests the data
el.testDispatchWithData();
expect(message).toBe("event dispatched");
message = "not dispatched";
el.testDispatchWithData();
expect(message).toBe("not dispatched");
});
});
});
("test-data-element")
class TestDataElement extends DataElement {
state = {
status: "default"
};
testDispatch() {
this.state = {
...this.state,
status: "changed"
};
this.dispatchChange();
}
}
("test-instance-data-element")
class TestInstanceDataElement extends DataElement {
static defaultState = { userName: "unknown" };
static get observedAttributes() { return ["user-id"]; }
static stateIdProperty = "userId";
static dataProperties = {
"user": {changeEvent:"user-changed"}
}
get userId():string { return this.getAttribute("user-id") || ""; }
set userId(stateId:string) { this.setAttribute("user-id", stateId); }
user = TestInstanceDataElement.defaultState;
attributeChangedCallback(name:string, oldValue:string, newValue:string) {
if (name === "user-id") {
this.refreshState({
user: TestInstanceDataElement.defaultState
});
}
}
testChange() {
this.user = {
...this.user,
userName: "joeuser"
};
this.dispatchChange("user");
}
testDispatch() {
this.dispatchChange("user");
}
testDispatchWithData() {
this.dispatchChange("user", {
...this.user,
userName: "janeuser"
});
}
}
("test-decorators", {
stateIdProperty: "dataId",
eventsListenAt: "self"
})
class TestDecorators extends DataElement {
()
user = { userName: "unknown" }
({changeEvent:"data-did-change"})
data = { test: "data"}
dataId = "1234";
}