DraftAssistant
for a Date field
WARNING
This example is not recommended for use in production, it is mainly intended for showcase purposes.
The Date
object is considered a legacy feature. It is recommended to use the Temporal API for new projects by MDN.
The following example aims to show how to define new assistants for specific needs.
Suppose that, for some reason, using a date picker for a Date field in a form is not possible.
ts
import {
FieldDraftAssistant,
SectionDraftAssistant,
Assertion,
LabelId,
type ModelFromContainer,
} from "self-assert";
export class DateDraftAssistant<
ContainerModel = any
> extends SectionDraftAssistant<Date, ContainerModel, [string]> {
static readonly defaultAssertionDescription = "Invalid date";
static for<ContainerModel = any>(
assertionId: LabelId,
modelFromContainer: ModelFromContainer<Date, ContainerModel>
): DateDraftAssistant<ContainerModel> {
const assertionIds = assertionId === "" ? [] : [assertionId];
/** @ts-expect-error /microsoft/TypeScript#5863 */
return this.with(
[this.createDateAssistant()],
(dateAsString) => this.createDate(assertionId, dateAsString),
modelFromContainer,
assertionIds
);
}
static forTopLevel(assertionId: LabelId) {
return this.for(assertionId, this.topLevelModelFromContainer());
}
static createDate(assertionId: LabelId, dateAsString: string) {
this.createAssertionFor(assertionId, dateAsString).mustHold();
return new Date(dateAsString);
}
static createDateAssistant() {
return FieldDraftAssistant.handling<Date>("", (date) =>
date.toISOString().substring(0, 10)
);
}
static createAssertionFor(assertionId: LabelId, dateAsString: string) {
return Assertion.requiring(
assertionId,
DateDraftAssistant.defaultAssertionDescription,
() =>
/^\d{4}-\d{2}-\d{2}$/.test(dateAsString) &&
!isNaN(new Date(dateAsString).getTime())
);
}
innerAssistant() {
return this.assistants[0];
}
setInnerModel(newModel: string) {
this.innerAssistant().setModel(newModel);
}
getInnerModel() {
return this.innerAssistant().getModel();
}
}
ts
import { describe, expect, it } from "@jest/globals";
import { DateDraftAssistant } from "./DateDraftAssistant";
import { DraftAssistant } from "self-assert";
describe("DateDraftAssistant", () => {
it("should handle ISO dates", (done) => {
const assistant = DateDraftAssistant.forTopLevel("");
assistant.setInnerModel("2020-01-01");
assistant.withCreatedModelDo(
(model) => {
expect(assistant.getInnerModel()).toBe("2020-01-01");
expect(model).toBeInstanceOf(Date);
expect(model.toISOString().startsWith("2020-01-01")).toBe(true);
done();
},
() => done("Should not be invalid")
);
});
it("should be invalid if the inner model does not have a valid ISO format", (done) => {
const assertionId = "ISODateAID";
const assistant = DateDraftAssistant.forTopLevel(assertionId);
assistant.setInnerModel("01/01/2020");
assistant.withCreatedModelDo(
() => done("Should be invalid"),
() => {
expect(DraftAssistant.isInvalidModel(assistant.getModel())).toBe(true);
expect(assistant.hasOnlyOneRuleBrokenIdentifiedAs(assertionId)).toBe(
true
);
expect(assistant.brokenRulesDescriptions()).toEqual([
DateDraftAssistant.defaultAssertionDescription,
]);
done();
}
);
});
it("should be invalid if the inner model has a valid format but is not an ISO date", (done) => {
const assertionId = "ISODateAID";
const assistant = DateDraftAssistant.forTopLevel(assertionId);
assistant.setInnerModel("2020-13-01");
assistant.withCreatedModelDo(
() => done("Should be invalid"),
() => {
expect(DraftAssistant.isInvalidModel(assistant.getModel())).toBe(true);
expect(assistant.hasOnlyOneRuleBrokenIdentifiedAs(assertionId)).toBe(
true
);
expect(assistant.brokenRulesDescriptions()).toEqual([
DateDraftAssistant.defaultAssertionDescription,
]);
done();
}
);
});
describe("inner assistant", () => {
it("should allow setting its value from an ISO date", () => {
const assistant = DateDraftAssistant.forTopLevel("");
assistant.innerAssistant().setModelFrom(new Date(2020, 0, 1));
expect(assistant.getInnerModel()).toBe("2020-01-01");
});
});
});
Breakdown
This example demonstrates a custom DraftAssistant
implementation for a specific case:
- It uses a
SectionDraftAssistant
that wraps aFieldDraftAssistant<string>
. - The user inputs a string expected to match the
YYYY-MM-DD
format (ISO date). - A validation rule ensures the string is a valid date in that format.
- If the validation passes, a
Date
object is created and returned as the model. - Otherwise, the model is considered invalid and validation errors are exposed.
Purpose
This showcases how to:
- Build custom assistants for specialized fields.
- Wrap legacy types in a safe and testable way.
- Validate string inputs before converting to more complex objects.
- Compose assistants to manage draft data at multiple levels.