Socket
Socket
Sign inDemoInstall

@desk-framework/frame-test

Package Overview
Dependencies
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@desk-framework/frame-test - npm Package Compare versions

Comparing version 4.0.0-dev.14 to 4.0.0-dev.15

dist/app/TestNavigationController.d.ts

18

.test-run/tests/app.js

@@ -1,2 +0,2 @@

import { app, bound, Activity, UIButton, UICell, UILabel, UITextField, ViewComposite, } from "@desk-framework/frame-core";
import { Activity, StringConvertible, ViewComposite, app, bound, ui, } from "@desk-framework/frame-core";
import { describe, expect, test, useTestContext } from "../../dist/index.js";

@@ -6,3 +6,3 @@ // ... from "@desk-framework/frame-test"

ready() {
this.view = new (UICell.with(UITextField.with({ value: bound("count"), onInput: "SetCount" }), UIButton.withLabel("+", "CountUp")))();
this.view = new (ui.cell(ui.textField({ value: bound("count"), onInput: "SetCount" }), ui.button("+", "CountUp")))();
app.showPage(this.view);

@@ -24,3 +24,3 @@ }

useTestContext((options) => {
options.path = "count";
options.navigationPageId = "count";
});

@@ -31,5 +31,5 @@ activity = new CountActivity();

test("Single view is rendered", async (t) => {
const MyView = ViewComposite.define((p) => UILabel.withText(p.title)).with({ title: "TEST" });
let view = new MyView();
app.showPage(view);
const MyView = ViewComposite.withPreset({ title: StringConvertible.EMPTY }, ui.label(bound.string("title"))).preset({ title: "TEST" });
let myView = new MyView();
app.showPage(myView);
await t.expectOutputAsync(100, { text: "TEST" });

@@ -42,6 +42,6 @@ });

// initial path should be set directly
expect(app.getPath()).toBe("count");
expect(app.activities.navigationController.pageId).toBe("count");
// setting another path takes some time
app.navigate("/another/path");
await t.expectPathAsync(100, "another/path");
app.navigate("/another/path/here");
await t.expectNavAsync(100, "another", "path/here");
// by then, the activity should be made inactive

@@ -48,0 +48,0 @@ await t.pollAsync(() => !activity.isActive(), 5, 100);

@@ -37,3 +37,3 @@ import { View, ViewClass } from "@desk-framework/frame-core";

styles?: Record<string, any>;
/** A base style class that must be applied to a matching element (e.g. a subclass of {@link UIButtonStyle}, {@link UICellStyle}, and {@link UIToggleStyle}) */
/** A base style class that must be applied to a matching element (i.e. a subclass of {@link UIStyle}) */
styleClass?: any;

@@ -40,0 +40,0 @@ }

import { ActivityContext, ConfigOptions, GlobalContext } from "@desk-framework/frame-core";
import { TestNavigationPath } from "./TestNavigationPath.js";
import { TestNavigationController } from "./TestNavigationController.js";
import { TestRenderer } from "../renderer/TestRenderer.js";

@@ -8,3 +8,3 @@ /** Type definition for the global {@link app} context with test-specific render and activity contexts, set by the {@link useTestContext} function */

activities: ActivityContext & {
navigationPath: TestNavigationPath;
navigationController: TestNavigationController;
};

@@ -17,6 +17,8 @@ };

export declare class TestContextOptions extends ConfigOptions {
/** The initial path on the history stack, defaults to empty string (i.e. `/`) */
path: string;
/** The initial page ID on the history stack, defaults to empty string */
navigationPageId: string;
/** The initial navigation detail on the history stack */
navigationDetail: string;
/** The delay (in milliseconds) after which path changes are applied, defaults to 5 */
pathDelay: number;
navigationDelay: number;
/** The frequency (in milliseconds) with which output is added, defaults to 15 */

@@ -23,0 +25,0 @@ renderFrequency: number;

import { ConfigOptions, app, } from "@desk-framework/frame-core";
import { TestScope } from "../TestScope.js";
import { TestTheme } from "../style/TestTheme.js";
import { TestNavigationPath } from "./TestNavigationPath.js";
import { TestNavigationController } from "./TestNavigationController.js";
import { TestRenderer } from "../renderer/TestRenderer.js";

@@ -12,6 +12,8 @@ import { TestViewportContext } from "./TestViewportContext.js";

export class TestContextOptions extends ConfigOptions {
/** The initial path on the history stack, defaults to empty string (i.e. `/`) */
path = "";
/** The initial page ID on the history stack, defaults to empty string */
navigationPageId = "";
/** The initial navigation detail on the history stack */
navigationDetail = "";
/** The delay (in milliseconds) after which path changes are applied, defaults to 5 */
pathDelay = 5;
navigationDelay = 5;
/** The frequency (in milliseconds) with which output is added, defaults to 15 */

@@ -61,4 +63,4 @@ renderFrequency = 15;

// create test navigation path and set initial path
app.activities.navigationPath = new TestNavigationPath(options);
app.activities.navigationController = new TestNavigationController(options);
return app;
}

@@ -47,3 +47,3 @@ import { RenderContext } from "@desk-framework/frame-core";

* A combination of all style overrides applied to this element
* - Styles are copied as specified in objects such as {@link UIButton.buttonStyle}, {@link UICell.cellStyle}, etc.
* - Styles are copied as specified in objects such as {@link UIButton.style}, {@link UICell.style}, etc.
* - While styles are usually applied to the rendered element in a platform-dependent way, the test renderer simply stores all properties in this object, which therefore has no specific type.

@@ -50,0 +50,0 @@ */

@@ -52,3 +52,3 @@ /** Running ID for generated elements */

* A combination of all style overrides applied to this element
* - Styles are copied as specified in objects such as {@link UIButton.buttonStyle}, {@link UICell.cellStyle}, etc.
* - Styles are copied as specified in objects such as {@link UIButton.style}, {@link UICell.style}, etc.
* - While styles are usually applied to the rendered element in a platform-dependent way, the test renderer simply stores all properties in this object, which therefore has no specific type.

@@ -55,0 +55,0 @@ */

@@ -6,3 +6,3 @@ import { Assertion, NegatedAssertion } from "./Assertion.js";

export { Assertion, NegatedAssertion, TestCase, TestScope, TestResult, TestResultsData, };
export * from "./app/TestNavigationPath.js";
export * from "./app/TestNavigationController.js";
export * from "./app/OutputAssertion.js";

@@ -9,0 +9,0 @@ export * from "./app/TestOutputElement.js";

@@ -5,3 +5,3 @@ import { Assertion, NegatedAssertion } from "./Assertion.js";

export { Assertion, NegatedAssertion, TestCase, TestScope, };
export * from "./app/TestNavigationPath.js";
export * from "./app/TestNavigationController.js";
export * from "./app/OutputAssertion.js";

@@ -8,0 +8,0 @@ export * from "./app/TestOutputElement.js";

@@ -1,2 +0,2 @@

import { Observer, RenderContext, UITheme, app, } from "@desk-framework/frame-core";
import { Observer, RenderContext, UIStyle, app, } from "@desk-framework/frame-core";
/** UI component event names that are used for basic platform events */

@@ -21,5 +21,5 @@ const _eventNames = {

let _baseStyles = new Map();
/** @internal Helper function to find the base style (class) from a style/overrides object (e.g. `UILabel.labelStyle`), if any */
/** @internal Helper function to find the base style (class) from a style/overrides object (e.g. `UILabel.style`), if any */
export function getBaseStyleClass(object) {
let base = object?.[UITheme.BaseStyle.OVERRIDES_BASE] || object;
let base = object?.[UIStyle.OVERRIDES_BASE] || object;
if (typeof base === "function")

@@ -26,0 +26,0 @@ return base;

@@ -5,9 +5,21 @@ import { Observer, RenderContext, View } from "@desk-framework/frame-core";

import { TestOutputElement } from "../app/TestOutputElement.js";
/**
* A class that represents a rendered message dialog (for testing)
* - This class can be used to validate the contents of a message dialog, and to click its buttons (asynchronously).
* - To wait for a message dialog to be rendered, use the {@link TestCase.expectMessageDialogAsync()} method and use the returned object.
* @hideconstructor
*/
export declare class RenderedTestMessageDialog {
constructor(dialogOutput: OutputAssertion);
/** The rendered label output elements */
labels: TestOutputElement[];
/** The rendered button output elements */
buttons: TestOutputElement[];
/** Clicks the specified button (matched using the button label) */
clickAsync(button: string): Promise<void>;
/** Clicks the first button of the dialog (confirm or dismiss button) */
confirmAsync(): Promise<void>;
/** Clicks the last button of the dialog (cancel or dismiss button) */
cancelAsync(): Promise<void>;
/** Clicks a button and idles while promises are resolved */
private _click;

@@ -46,3 +58,3 @@ }

clear(): this;
/** Re-mounts mounted content (not supported in test renderer) */
/** Re-mounts mounted content (not supported in test renderer, but does emit a change event) */
remount(): this;

@@ -89,2 +101,32 @@ /** Returns true if any output is currently rendered at all */

expectOutputAsync(timeout: number, ...select: OutputSelectFilter[]): Promise<OutputAssertion>;
/**
* Waits for an alert or confirmation dialog to be rendered, that contains the provided label(s)
* - To avoid casting {@link app} to get to the {@link TestRenderer} instance, use the {@link TestCase.expectMessageDialogAsync()} method instead.
* @note This method is asynchronous, and **must** be `await`-ed in a test function.
*
* @summary
* This method regularly polls all rendered output, and attempts to find a message dialog. As soon as one is found, the resulting promise is resolved to an instance of {@link RenderedTestMessageDialog}.
*
* The returned object can be used to validate dialog contents, and to click its buttons (asynchronously).
* If the specified timeout is reached, the promise is rejected.
*
* @param timeout The number of milliseconds to wait for matching output to be rendered
* @param match A list of strings or regular expressions to validate matching label(s) on the message dialog.
* @returns A promise for an {@link RenderedTestMessageDialog} instance. The promise is rejected if a timeout occurs.
*
* @example
* describe("My scope", () => {
* test("Cancel a confirmation dialog", async (t) => {
* let app = useTestContext();
* // ...
* let p = app.showConfirmDialog("Are you sure?");
* await (
* await app.renderer.expectMessageDialogAsync(100, /sure/)
* ).cancelAsync();
* let result = await p;
* expect(result).toBe(false);
* });
* });
*/
expectMessageDialogAsync(timeout: number, ...match: Array<string | RegExp>): Promise<RenderedTestMessageDialog>;

@@ -91,0 +133,0 @@ /** Returns an object representation of (selected) output */

@@ -9,2 +9,8 @@ import { RenderContext, app, } from "@desk-framework/frame-core";

const MAX_SCHED_RUNTIME = 30;
/**
* A class that represents a rendered message dialog (for testing)
* - This class can be used to validate the contents of a message dialog, and to click its buttons (asynchronously).
* - To wait for a message dialog to be rendered, use the {@link TestCase.expectMessageDialogAsync()} method and use the returned object.
* @hideconstructor
*/
export class RenderedTestMessageDialog {

@@ -15,4 +21,7 @@ constructor(dialogOutput) {

}
/** The rendered label output elements */
labels = [];
/** The rendered button output elements */
buttons = [];
/** Clicks the specified button (matched using the button label) */
async clickAsync(button) {

@@ -25,5 +34,7 @@ for (let b of this.buttons) {

}
/** Clicks the first button of the dialog (confirm or dismiss button) */
async confirmAsync() {
return this._click(this.buttons[0]);
}
/** Clicks the last button of the dialog (cancel or dismiss button) */
async cancelAsync() {

@@ -33,2 +44,3 @@ let button = this.buttons[this.buttons.length - 1];

}
/** Clicks a button and idles while promises are resolved */
async _click(button) {

@@ -124,4 +136,5 @@ if (!button)

}
/** Re-mounts mounted content (not supported in test renderer) */
/** Re-mounts mounted content (not supported in test renderer, but does emit a change event) */
remount() {
this.emitChange();
return this;

@@ -198,2 +211,32 @@ }

}
/**
* Waits for an alert or confirmation dialog to be rendered, that contains the provided label(s)
* - To avoid casting {@link app} to get to the {@link TestRenderer} instance, use the {@link TestCase.expectMessageDialogAsync()} method instead.
* @note This method is asynchronous, and **must** be `await`-ed in a test function.
*
* @summary
* This method regularly polls all rendered output, and attempts to find a message dialog. As soon as one is found, the resulting promise is resolved to an instance of {@link RenderedTestMessageDialog}.
*
* The returned object can be used to validate dialog contents, and to click its buttons (asynchronously).
* If the specified timeout is reached, the promise is rejected.
*
* @param timeout The number of milliseconds to wait for matching output to be rendered
* @param match A list of strings or regular expressions to validate matching label(s) on the message dialog.
* @returns A promise for an {@link RenderedTestMessageDialog} instance. The promise is rejected if a timeout occurs.
*
* @example
* describe("My scope", () => {
* test("Cancel a confirmation dialog", async (t) => {
* let app = useTestContext();
* // ...
* let p = app.showConfirmDialog("Are you sure?");
* await (
* await app.renderer.expectMessageDialogAsync(100, /sure/)
* ).cancelAsync();
* let result = await p;
* expect(result).toBe(false);
* });
* });
*/
async expectMessageDialogAsync(timeout, ...match) {

@@ -200,0 +243,0 @@ let dialogOut = await this.expectOutputAsync(timeout, {

@@ -1,2 +0,2 @@

import { RenderContext, UIButtonStyle, } from "@desk-framework/frame-core";
import { RenderContext, ui, } from "@desk-framework/frame-core";
import { TestOutputElement } from "../app/TestOutputElement.js";

@@ -9,3 +9,3 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js";

.observe(observed)
.observePropertyAsync("label", "icon", "chevron", "disabled", "width", "pressed", "buttonStyle");
.observePropertyAsync("label", "icon", "chevron", "disabled", "width", "pressed", "style");
}

@@ -23,3 +23,3 @@ async handlePropertyChange(property, value, event) {

case "width":
case "buttonStyle":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -58,5 +58,6 @@ return;

element.styleClass =
getBaseStyleClass(button.buttonStyle) || UIButtonStyle;
getBaseStyleClass(button.style) ||
(button.primary ? ui.style.BUTTON_PRIMARY : ui.style.BUTTON);
applyElementStyle(element, [
button.buttonStyle,
button.style,
button.width !== undefined

@@ -63,0 +64,0 @@ ? { width: button.width, minWidth: 0 }

@@ -1,2 +0,2 @@

import { RenderContext, UICellStyle, } from "@desk-framework/frame-core";
import { RenderContext, ui, } from "@desk-framework/frame-core";
import { TestOutputElement } from "../app/TestOutputElement.js";

@@ -10,3 +10,3 @@ import { getBaseStyleClass } from "./TestBaseObserver.js";

// note some properties are handled by container (e.g. padding)
"textDirection", "margin", "borderRadius", "background", "textColor", "opacity", "dropShadow", "cellStyle");
"textDirection", "margin", "borderRadius", "background", "textColor", "opacity", "style");
}

@@ -22,4 +22,3 @@ async handlePropertyChange(property, value, event) {

case "opacity":
case "dropShadow":
case "cellStyle":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -46,4 +45,4 @@ return;

// NOTE: margin, textDirection aren't applied in test renderer
super.updateStyle(element, getBaseStyleClass(cell.cellStyle) || UICellStyle, [
cell.cellStyle,
super.updateStyle(element, getBaseStyleClass(cell.style) || ui.style.CELL, [
cell.style,
{

@@ -55,3 +54,2 @@ padding: cell.padding,

opacity: cell.opacity,
dropShadow: cell.dropShadow,
},

@@ -58,0 +56,0 @@ ]);

@@ -1,2 +0,2 @@

import { RenderContext, UIImageStyle, } from "@desk-framework/frame-core";
import { RenderContext, ui, } from "@desk-framework/frame-core";
import { TestOutputElement } from "../app/TestOutputElement.js";

@@ -7,3 +7,3 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js";

observe(observed) {
return super.observe(observed).observePropertyAsync("url", "imageStyle");
return super.observe(observed).observePropertyAsync("url", "style");
}

@@ -16,3 +16,3 @@ async handlePropertyChange(property, value, event) {

return;
case "imageStyle":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -37,4 +37,4 @@ return;

if (image) {
element.styleClass = getBaseStyleClass(image.imageStyle) || UIImageStyle;
applyElementStyle(element, [image.imageStyle, { width: image.width, height: image.height }], image.position);
element.styleClass = getBaseStyleClass(image.style) || ui.style.IMAGE;
applyElementStyle(element, [image.style, { width: image.width, height: image.height }], image.position);
}

@@ -41,0 +41,0 @@ }

@@ -1,2 +0,2 @@

import { RenderContext, UILabelStyle, } from "@desk-framework/frame-core";
import { RenderContext, ui, } from "@desk-framework/frame-core";
import { TestOutputElement } from "../app/TestOutputElement.js";

@@ -9,3 +9,3 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js";

.observe(observed)
.observePropertyAsync("text", "icon", "bold", "italic", "color", "width", "labelStyle");
.observePropertyAsync("text", "icon", "bold", "italic", "color", "width", "dim", "style");
}

@@ -23,3 +23,4 @@ async handlePropertyChange(property, value, event) {

case "width":
case "labelStyle":
case "dim":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -44,5 +45,11 @@ return;

if (label) {
element.styleClass = getBaseStyleClass(label.labelStyle) || UILabelStyle;
element.styleClass =
getBaseStyleClass(label.style) ||
(label.title
? ui.style.LABEL_TITLE
: label.small
? ui.style.LABEL_SMALL
: ui.style.LABEL);
applyElementStyle(element, [
label.labelStyle,
label.style,
{

@@ -53,2 +60,5 @@ width: label.width,

textColor: label.color,
opacity: label.dim === true ? 0.5 : label.dim === false ? 1 : label.dim,
lineBreakMode: label.wrap ? "pre-wrap" : undefined,
userSelect: label.selectable || undefined,
},

@@ -62,4 +72,4 @@ ], label.position);

element.text = String(this.observed.text);
element.icon = String(this.observed.icon);
element.icon = String(this.observed.icon || "");
}
}

@@ -1,2 +0,2 @@

import { RenderContext, UITextFieldStyle, } from "@desk-framework/frame-core";
import { RenderContext, ui, } from "@desk-framework/frame-core";
import { TestOutputElement } from "../app/TestOutputElement.js";

@@ -9,3 +9,3 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js";

.observe(observed)
.observePropertyAsync("placeholder", "value", "disabled", "readOnly", "width", "textFieldStyle");
.observePropertyAsync("placeholder", "value", "disabled", "readOnly", "width", "style");
}

@@ -22,3 +22,3 @@ async handlePropertyChange(property, value, event) {

case "width":
case "textFieldStyle":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -55,5 +55,5 @@ return;

element.styleClass =
getBaseStyleClass(textField.textFieldStyle) || UITextFieldStyle;
getBaseStyleClass(textField.style) || ui.style.TEXTFIELD;
applyElementStyle(element, [
textField.textFieldStyle,
textField.style,
textField.width !== undefined

@@ -60,0 +60,0 @@ ? { width: textField.width, minWidth: 0 }

@@ -1,2 +0,2 @@

import { RenderContext, UIToggleStyle, } from "@desk-framework/frame-core";
import { RenderContext, ui, } from "@desk-framework/frame-core";
import { TestOutputElement } from "../app/TestOutputElement.js";

@@ -9,3 +9,3 @@ import { TestBaseObserver, applyElementStyle, getBaseStyleClass, } from "./TestBaseObserver.js";

.observe(observed)
.observePropertyAsync("label", "state", "disabled", "toggleStyle", "labelStyle");
.observePropertyAsync("label", "state", "disabled", "style", "labelStyle");
}

@@ -20,3 +20,3 @@ async handlePropertyChange(property, value, event) {

case "disabled":
case "toggleStyle":
case "style":
case "labelStyle":

@@ -52,5 +52,4 @@ this.scheduleUpdate(undefined, this.element);

// set styles
element.styleClass =
getBaseStyleClass(toggle.toggleStyle) || UIToggleStyle;
applyElementStyle(element, [toggle.toggleStyle], toggle.position);
element.styleClass = getBaseStyleClass(toggle.style) || ui.style.TOGGLE;
applyElementStyle(element, [toggle.style], toggle.position);
}

@@ -57,0 +56,0 @@ }

import { UIColor } from "@desk-framework/frame-core";
/** @internal Default set of colors */
export const colors = [
["black", new UIColor("#000000")],
["darkerGray", new UIColor("#333333")],
["darkGray", new UIColor("#777777")],
["gray", new UIColor("#aaaaaa")],
["lightGray", new UIColor("#dddddd")],
["white", new UIColor("#ffffff")],
["slate", new UIColor("#667788")],
["lightSlate", new UIColor("#c0c8d0")],
["red", new UIColor("#ee3333")],
["orange", new UIColor("#ee9922")],
["yellow", new UIColor("#ddcc33")],
["lime", new UIColor("#99bb33")],
["green", new UIColor("#44aa44")],
["turquoise", new UIColor("#33aaaa")],
["cyan", new UIColor("#33bbbb")],
["blue", new UIColor("#2277ff")],
["violet", new UIColor("#8844ee")],
["purple", new UIColor("#aa4488")],
["magenta", new UIColor("#dd2299")],
["primary", new UIColor("@blue")],
["primaryBackground", new UIColor("@blue")],
["accent", new UIColor("@purple")],
["pageBackground", new UIColor("@background")],
["background", new UIColor("@white")],
["text", new UIColor("@background").text()],
["separator", new UIColor("@background").text().alpha(0.1)],
["controlBase", new UIColor("@background").contrast(-0.1)],
["modalShade", new UIColor("@black")],
["Black", new UIColor("#000000")],
["DarkerGray", new UIColor("#333333")],
["DarkGray", new UIColor("#777777")],
["Gray", new UIColor("#aaaaaa")],
["LightGray", new UIColor("#dddddd")],
["White", new UIColor("#ffffff")],
["Slate", new UIColor("#667788")],
["LightSlate", new UIColor("#c0c8d0")],
["Red", new UIColor("#ee3333")],
["Orange", new UIColor("#ee9922")],
["Yellow", new UIColor("#ddcc33")],
["Lime", new UIColor("#99bb33")],
["Green", new UIColor("#44aa44")],
["Turquoise", new UIColor("#33aaaa")],
["Cyan", new UIColor("#33bbbb")],
["Blue", new UIColor("#2277ff")],
["Violet", new UIColor("#8844ee")],
["Purple", new UIColor("#aa4488")],
["Magenta", new UIColor("#dd2299")],
["Separator", new UIColor("Background").text().alpha(0.1)],
["ControlBase", new UIColor("Background").contrast(-0.1)],
["Background", new UIColor("White")],
["Text", new UIColor("Background").text()],
["Danger", new UIColor("Red")],
["DangerBackground", new UIColor("Danger")],
["Success", new UIColor("Green")],
["SuccessBackground", new UIColor("Success")],
["Primary", new UIColor("Blue")],
["PrimaryBackground", new UIColor("Primary")],
["Accent", new UIColor("Purple")],
["AccentBackground", new UIColor("Accent")],
["Brand", new UIColor("Primary")],
["BrandBackground", new UIColor("Brand")],
];
import { UIIconResource } from "@desk-framework/frame-core";
/** @internal SVG icon set */
export const icons = [
["blank", new UIIconResource(`<svg viewBox="0 0 48 48"></svg>`)],
["Blank", new UIIconResource(`<svg></svg>`)],
["Close", new UIIconResource(`<svg id="test-close"></svg>`)],
["Check", new UIIconResource(`<svg id="test-check"></svg>`)],
["ChevronDown", new UIIconResource(`<svg id="test-chevronDown"></svg>`)],
["ChevronUp", new UIIconResource(`<svg id="test-chevronUp"></svg>`)],
[
"close",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M39 12l-3-3l-12 12l-12-12l-3 3l12 12l-12 12l3 3l12-12l12 12l3-3l-12-12l12-12z"/></svg>`),
"ChevronNext",
new UIIconResource(`<svg id="test-chevronNext"></svg>`).setMirrorRTL(),
],
[
"check",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M18 32l-8-8l-3 3L18 38l24-24-3-3z"/></svg>`),
"ChevronBack",
new UIIconResource(`<svg id="test-chevronBack"></svg>`).setMirrorRTL(),
],
[
"chevronDown",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M32 18l-9 9l-9-9l-3 3l12 12l12-12z"/></svg>`),
],
[
"chevronUp",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M32 30l-9-9l-9 9l-3-3l12-12l12 12z"/></svg>`),
],
[
"chevronNext",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M18 32l9-9l-9-9l3-3l12 12l-12 12z"/></svg>`).setMirrorRTL(),
],
[
"chevronBack",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M30 32l-9-9l9-9l-3-3l-12 12l12 12z"/></svg>`).setMirrorRTL(),
],
[
"plus",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M8 20h14V6h4v14h14v4H26v14h-4V24H8z"/></svg>`),
],
[
"minus",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M8 20H40v4H8z"/></svg>`),
],
[
"menu",
new UIIconResource(`<svg viewBox="0 0 48 48"><path d="M8 10H40v4H8zM8 20H40v4H8zM8 30H40v4H8z"/></svg>`),
],
[
"more",
new UIIconResource(`<svg viewBox="0 0 48 48"><circle cx="24"cy="10"r="4"/><circle cx="24"cy="24"r="4"/><circle cx="24"cy="38"r="4"/></svg>`),
],
["Plus", new UIIconResource(`<svg id="test-plus"></svg>`)],
["Minus", new UIIconResource(`<svg id="test-minus"></svg>`)],
["Menu", new UIIconResource(`<svg id="test-menu"></svg>`)],
["More", new UIIconResource(`<svg id="test-more"></svg>`)],
];

@@ -1,2 +0,2 @@

import { UICell, UIViewRenderer, ViewComposite, app, bound, } from "@desk-framework/frame-core";
import { ViewComposite, app, bound, ui, } from "@desk-framework/frame-core";
/** @internal Limited implementation of a dialog controller */

@@ -10,3 +10,3 @@ export class TestDialog extends ViewComposite {

createView() {
return new (UICell.with(UIViewRenderer.with({
return new (ui.cell(ui.renderView({
view: bound("dialogView"),

@@ -13,0 +13,0 @@ onViewUnlinked: "DialogViewUnlinked",

@@ -1,2 +0,2 @@

import { app, strf, UIButton, UICell, UILabel, ViewComposite, } from "@desk-framework/frame-core";
import { app, strf, ui, ViewComposite, } from "@desk-framework/frame-core";
/** @internal Limited implementation of a message dialog controller, that can be used to test for message display and button presses */

@@ -36,11 +36,11 @@ export class TestMessageDialog extends ViewComposite {

createView() {
return new (UICell.with({ accessibleRole: "alertdialog" }, ...this.options.messages.map((text) => UILabel.withText(text)), UIButton.with({
return new (ui.column({ accessibleRole: "alertdialog" }, ...this.options.messages.map((text) => ui.label(String(text))), ui.button({
label: this.confirmLabel,
onClick: "+Confirm",
requestFocus: true,
}), UIButton.with({
}), ui.button({
hidden: !this.otherLabel,
label: this.otherLabel,
onClick: "+Other",
}), UIButton.with({
}), ui.button({
hidden: !this.cancelLabel,

@@ -47,0 +47,0 @@ label: this.cancelLabel,

import { Assertion } from "./Assertion.js";
import { TestScope } from "./TestScope.js";
import { OutputAssertion, OutputSelectFilter } from "./app/OutputAssertion.js";
import { RenderedTestMessageDialog } from "./renderer/TestRenderer.js";
/**

@@ -204,8 +205,9 @@ * A class that represents a single test case, part of a {@link TestScope}

/**
* Waits for the global navigation path to match the given string
* Waits for the global navigation location to match the given page ID and detail
*
* @summary This method starts checking the navigation path periodically (using {@link GlobalContext.getPath()}), and waits for the path to match the provided string. If the path still doesn't match after the given timeout (number of milliseconds) this method throws an error.
* @summary This method starts checking the navigation controller periodically, and waits for the path to match the provided string. If the path still doesn't match after the given timeout (number of milliseconds) this method throws an error.
* @note This method is asynchronous and **must** be `await`-ed.
* @param timeout Timeout, in milliseconds
* @param path Path to wait for, must be an exact match
* @param pageId Page ID to wait for, must be an exact match
* @param detail Detail string to wait for (defaults to empty string), must be an exact match
* @returns A promise (void) that's resolved when the patch matches, or rejected when a timeout occurs.

@@ -215,9 +217,9 @@ *

* describe("My scope", () => {
* test("Wait for path", async (t) => {
* test("Wait for navigation", async (t) => {
* // ... navigate to a path somehow
* await t.expectPathAsync(100, "foo/bar");
* await t.expectNavAsync(100, "foo");
* });
* });
*/
expectPathAsync(timeout: number, path: string): Promise<void>;
expectNavAsync(timeout: number, pageId: string, detail?: string): Promise<void>;
/**

@@ -240,3 +242,24 @@ * Waits for output to be rendered (by the test renderer) that matches the provided filters

expectOutputAsync(timeout: number, ...select: OutputSelectFilter[]): Promise<OutputAssertion>;
expectMessageDialogAsync(timeout: number, ...match: Array<string | RegExp>): Promise<import("./renderer/TestRenderer.js").RenderedTestMessageDialog>;
/**
* Waits for an alert or confirm dialog to be rendered (by the test renderer)
* - This method uses {@link TestRenderer.expectMessageDialogAsync()}, refer to its documentation for details.
* - This method is asynchronous and **must** be `await`-ed.
* @param timeout Timeout, in milliseconds
* @param match A list of strings or regular expressions to match the dialog message
* @returns A promise that's resolved to a {@link RenderedTestMessageDialog} instance for checking content or pressing buttons, or rejected when a timeout occurs.
*
* @example
* describe("My scope", () => {
* test("Cancel confirm dialog", async (t) => {
* // ...
* let p = app.showConfirmDialog("Are you sure?");
* await (
* await t.expectMessageDialogAsync(100, /sure/)
* ).cancelAsync();
* let result = await p;
* expect(result).toBe(false);
* });
* });
*/
expectMessageDialogAsync(timeout: number, ...match: Array<string | RegExp>): Promise<RenderedTestMessageDialog>;
/** Runs this test case (used by {@link TestScope}) */

@@ -243,0 +266,0 @@ runTestAsync(timeout?: number): Promise<void>;

import { app } from "@desk-framework/frame-core";
import { Assertion } from "./Assertion.js";
import { OutputAssertion } from "./app/OutputAssertion.js";
import { TestRenderer } from "./renderer/TestRenderer.js";
import { TestRenderer, } from "./renderer/TestRenderer.js";
import { val2str } from "./log.js";

@@ -314,8 +314,9 @@ const DEFAULT_TIMEOUT = 10000;

/**
* Waits for the global navigation path to match the given string
* Waits for the global navigation location to match the given page ID and detail
*
* @summary This method starts checking the navigation path periodically (using {@link GlobalContext.getPath()}), and waits for the path to match the provided string. If the path still doesn't match after the given timeout (number of milliseconds) this method throws an error.
* @summary This method starts checking the navigation controller periodically, and waits for the path to match the provided string. If the path still doesn't match after the given timeout (number of milliseconds) this method throws an error.
* @note This method is asynchronous and **must** be `await`-ed.
* @param timeout Timeout, in milliseconds
* @param path Path to wait for, must be an exact match
* @param pageId Page ID to wait for, must be an exact match
* @param detail Detail string to wait for (defaults to empty string), must be an exact match
* @returns A promise (void) that's resolved when the patch matches, or rejected when a timeout occurs.

@@ -325,10 +326,16 @@ *

* describe("My scope", () => {
* test("Wait for path", async (t) => {
* test("Wait for navigation", async (t) => {
* // ... navigate to a path somehow
* await t.expectPathAsync(100, "foo/bar");
* await t.expectNavAsync(100, "foo");
* });
* });
*/
async expectPathAsync(timeout, path) {
await this.pollAsync(() => app.getPath() === path, 5, timeout, () => Error(`Expected path ${val2str(path)} but it is ${val2str(app.getPath())}`));
async expectNavAsync(timeout, pageId, detail = "") {
await this.pollAsync(() => app.activities.navigationController.pageId === pageId &&
app.activities.navigationController.detail === detail, 5, timeout, () => Error("Expected navigation to " +
val2str(pageId + "/" + detail) +
" but location is " +
val2str(app.activities.navigationController.pageId +
"/" +
app.activities.navigationController.detail)));
}

@@ -369,2 +376,23 @@ /**

}
/**
* Waits for an alert or confirm dialog to be rendered (by the test renderer)
* - This method uses {@link TestRenderer.expectMessageDialogAsync()}, refer to its documentation for details.
* - This method is asynchronous and **must** be `await`-ed.
* @param timeout Timeout, in milliseconds
* @param match A list of strings or regular expressions to match the dialog message
* @returns A promise that's resolved to a {@link RenderedTestMessageDialog} instance for checking content or pressing buttons, or rejected when a timeout occurs.
*
* @example
* describe("My scope", () => {
* test("Cancel confirm dialog", async (t) => {
* // ...
* let p = app.showConfirmDialog("Are you sure?");
* await (
* await t.expectMessageDialogAsync(100, /sure/)
* ).cancelAsync();
* let result = await p;
* expect(result).toBe(false);
* });
* });
*/
async expectMessageDialogAsync(timeout, ...match) {

@@ -371,0 +399,0 @@ if (!(app.renderer instanceof TestRenderer)) {

{
"name": "@desk-framework/frame-test",
"version": "4.0.0-dev.14",
"version": "4.0.0-dev.15",
"publishConfig": {

@@ -32,3 +32,3 @@ "tag": "next"

"peerDependencies": {
"@desk-framework/frame-core": "4.0.0-dev.14"
"@desk-framework/frame-core": "4.0.0-dev.15"
},

@@ -35,0 +35,0 @@ "devDependencies": {

@@ -38,3 +38,3 @@ import { View, ViewClass } from "@desk-framework/frame-core";

styles?: Record<string, any>;
/** A base style class that must be applied to a matching element (e.g. a subclass of {@link UIButtonStyle}, {@link UICellStyle}, and {@link UIToggleStyle}) */
/** A base style class that must be applied to a matching element (i.e. a subclass of {@link UIStyle}) */
styleClass?: any;

@@ -41,0 +41,0 @@ }

@@ -9,3 +9,3 @@ import {

import { TestTheme } from "../style/TestTheme.js";
import { TestNavigationPath } from "./TestNavigationPath.js";
import { TestNavigationController } from "./TestNavigationController.js";
import { TestRenderer } from "../renderer/TestRenderer.js";

@@ -17,3 +17,5 @@ import { TestViewportContext } from "./TestViewportContext.js";

renderer: TestRenderer;
activities: ActivityContext & { navigationPath: TestNavigationPath };
activities: ActivityContext & {
navigationController: TestNavigationController;
};
};

@@ -26,7 +28,10 @@

export class TestContextOptions extends ConfigOptions {
/** The initial path on the history stack, defaults to empty string (i.e. `/`) */
path = "";
/** The initial page ID on the history stack, defaults to empty string */
navigationPageId = "";
/** The initial navigation detail on the history stack */
navigationDetail = "";
/** The delay (in milliseconds) after which path changes are applied, defaults to 5 */
pathDelay = 5;
navigationDelay = 5;

@@ -83,5 +88,5 @@ /** The frequency (in milliseconds) with which output is added, defaults to 15 */

// create test navigation path and set initial path
app.activities.navigationPath = new TestNavigationPath(options);
app.activities.navigationController = new TestNavigationController(options);
return app as TestContext;
}

@@ -74,3 +74,3 @@ import { RenderContext } from "@desk-framework/frame-core";

* A combination of all style overrides applied to this element
* - Styles are copied as specified in objects such as {@link UIButton.buttonStyle}, {@link UICell.cellStyle}, etc.
* - Styles are copied as specified in objects such as {@link UIButton.style}, {@link UICell.style}, etc.
* - While styles are usually applied to the rendered element in a platform-dependent way, the test renderer simply stores all properties in this object, which therefore has no specific type.

@@ -77,0 +77,0 @@ */

@@ -14,3 +14,3 @@ import { Assertion, NegatedAssertion } from "./Assertion.js";

};
export * from "./app/TestNavigationPath.js";
export * from "./app/TestNavigationController.js";
export * from "./app/OutputAssertion.js";

@@ -17,0 +17,0 @@ export * from "./app/TestOutputElement.js";

@@ -7,3 +7,3 @@ import {

UIContainer,
UITheme,
UIStyle,
app,

@@ -35,7 +35,5 @@ } from "@desk-framework/frame-core";

/** @internal Helper function to find the base style (class) from a style/overrides object (e.g. `UILabel.labelStyle`), if any */
export function getBaseStyleClass(
object: UITheme.StyleConfiguration<any>,
): undefined | (new () => UITheme.BaseStyle<string, any>) {
let base = (object as any)?.[UITheme.BaseStyle.OVERRIDES_BASE] || object;
/** @internal Helper function to find the base style (class) from a style/overrides object (e.g. `UILabel.style`), if any */
export function getBaseStyleClass(object: any): undefined | UIStyle.Type<any> {
let base = (object as any)?.[UIStyle.OVERRIDES_BASE] || object;
if (typeof base === "function") return base;

@@ -50,10 +48,5 @@ }

/** @internal Helper function to get styles from a (base) style class */
export function getClassStyles(
styleClass: new () => UITheme.BaseStyle<string, any>,
) {
export function getClassStyles(styleClass: UIStyle.Type<any>) {
if (!_baseStyles.has(styleClass)) {
_baseStyles.set(
styleClass,
(new styleClass() as UITheme.BaseStyle<string, any>).getStyles(),
);
_baseStyles.set(styleClass, (new styleClass() as UIStyle<any>).getStyles());
}

@@ -60,0 +53,0 @@ return _baseStyles.get(styleClass)!;

@@ -18,2 +18,8 @@ import {

/**
* A class that represents a rendered message dialog (for testing)
* - This class can be used to validate the contents of a message dialog, and to click its buttons (asynchronously).
* - To wait for a message dialog to be rendered, use the {@link TestCase.expectMessageDialogAsync()} method and use the returned object.
* @hideconstructor
*/
export class RenderedTestMessageDialog {

@@ -25,5 +31,9 @@ constructor(dialogOutput: OutputAssertion) {

/** The rendered label output elements */
labels: TestOutputElement[] = [];
/** The rendered button output elements */
buttons: TestOutputElement[] = [];
/** Clicks the specified button (matched using the button label) */
async clickAsync(button: string) {

@@ -36,2 +46,3 @@ for (let b of this.buttons) {

/** Clicks the first button of the dialog (confirm or dismiss button) */
async confirmAsync() {

@@ -41,2 +52,3 @@ return this._click(this.buttons[0]);

/** Clicks the last button of the dialog (cancel or dismiss button) */
async cancelAsync() {

@@ -47,2 +59,3 @@ let button = this.buttons[this.buttons.length - 1];

/** Clicks a button and idles while promises are resolved */
private async _click(button?: TestOutputElement) {

@@ -151,4 +164,5 @@ if (!button) throw Error("Message dialog button not found");

/** Re-mounts mounted content (not supported in test renderer) */
/** Re-mounts mounted content (not supported in test renderer, but does emit a change event) */
remount() {
this.emitChange();
return this;

@@ -231,2 +245,32 @@ }

/**
* Waits for an alert or confirmation dialog to be rendered, that contains the provided label(s)
* - To avoid casting {@link app} to get to the {@link TestRenderer} instance, use the {@link TestCase.expectMessageDialogAsync()} method instead.
* @note This method is asynchronous, and **must** be `await`-ed in a test function.
*
* @summary
* This method regularly polls all rendered output, and attempts to find a message dialog. As soon as one is found, the resulting promise is resolved to an instance of {@link RenderedTestMessageDialog}.
*
* The returned object can be used to validate dialog contents, and to click its buttons (asynchronously).
* If the specified timeout is reached, the promise is rejected.
*
* @param timeout The number of milliseconds to wait for matching output to be rendered
* @param match A list of strings or regular expressions to validate matching label(s) on the message dialog.
* @returns A promise for an {@link RenderedTestMessageDialog} instance. The promise is rejected if a timeout occurs.
*
* @example
* describe("My scope", () => {
* test("Cancel a confirmation dialog", async (t) => {
* let app = useTestContext();
* // ...
* let p = app.showConfirmDialog("Are you sure?");
* await (
* await app.renderer.expectMessageDialogAsync(100, /sure/)
* ).cancelAsync();
* let result = await p;
* expect(result).toBe(false);
* });
* });
*/
async expectMessageDialogAsync(

@@ -233,0 +277,0 @@ timeout: number,

@@ -5,3 +5,3 @@ import {

UIButton,
UIButtonStyle,
ui,
} from "@desk-framework/frame-core";

@@ -27,3 +27,3 @@ import { TestOutputElement } from "../app/TestOutputElement.js";

"pressed",
"buttonStyle",
"style",
);

@@ -47,3 +47,3 @@ }

case "width":
case "buttonStyle":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -90,7 +90,8 @@ return;

element.styleClass =
getBaseStyleClass(button.buttonStyle) || UIButtonStyle;
getBaseStyleClass(button.style) ||
(button.primary ? ui.style.BUTTON_PRIMARY : ui.style.BUTTON);
applyElementStyle(
element,
[
button.buttonStyle,
button.style,
button.width !== undefined

@@ -97,0 +98,0 @@ ? { width: button.width, minWidth: 0 }

@@ -5,3 +5,3 @@ import {

UICell,
UICellStyle,
ui,
} from "@desk-framework/frame-core";

@@ -23,4 +23,3 @@ import { TestOutputElement } from "../app/TestOutputElement.js";

"opacity",
"dropShadow",
"cellStyle",
"style",
);

@@ -42,4 +41,3 @@ }

case "opacity":
case "dropShadow":
case "cellStyle":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -67,18 +65,13 @@ return;

// NOTE: margin, textDirection aren't applied in test renderer
super.updateStyle(
element,
getBaseStyleClass(cell.cellStyle) || UICellStyle,
[
cell.cellStyle,
{
padding: cell.padding,
borderRadius: cell.borderRadius,
background: cell.background,
textColor: cell.textColor,
opacity: cell.opacity,
dropShadow: cell.dropShadow,
},
],
);
super.updateStyle(element, getBaseStyleClass(cell.style) || ui.style.CELL, [
cell.style,
{
padding: cell.padding,
borderRadius: cell.borderRadius,
background: cell.background,
textColor: cell.textColor,
opacity: cell.opacity,
},
]);
}
}

@@ -8,3 +8,3 @@ import {

UIScrollContainer,
UITheme,
UIStyle,
View,

@@ -106,3 +106,3 @@ app,

element: TestOutputElement,
BaseStyle?: new () => UITheme.BaseStyle<string, any>,
BaseStyle?: UIStyle.Type<any>,
styles?: any[],

@@ -109,0 +109,0 @@ ) {

@@ -5,3 +5,3 @@ import {

UIImage,
UIImageStyle,
ui,
} from "@desk-framework/frame-core";

@@ -18,3 +18,3 @@ import { TestOutputElement } from "../app/TestOutputElement.js";

override observe(observed: UIImage) {
return super.observe(observed).observePropertyAsync("url", "imageStyle");
return super.observe(observed).observePropertyAsync("url", "style");
}

@@ -32,3 +32,3 @@

return;
case "imageStyle":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -54,6 +54,6 @@ return;

if (image) {
element.styleClass = getBaseStyleClass(image.imageStyle) || UIImageStyle;
element.styleClass = getBaseStyleClass(image.style) || ui.style.IMAGE;
applyElementStyle(
element,
[image.imageStyle, { width: image.width, height: image.height }],
[image.style, { width: image.width, height: image.height }],
image.position,

@@ -60,0 +60,0 @@ );

@@ -5,3 +5,3 @@ import {

UILabel,
UILabelStyle,
ui,
} from "@desk-framework/frame-core";

@@ -27,3 +27,4 @@ import { TestOutputElement } from "../app/TestOutputElement.js";

"width",
"labelStyle",
"dim",
"style",
);

@@ -47,3 +48,4 @@ }

case "width":
case "labelStyle":
case "dim":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -69,7 +71,13 @@ return;

if (label) {
element.styleClass = getBaseStyleClass(label.labelStyle) || UILabelStyle;
element.styleClass =
getBaseStyleClass(label.style) ||
(label.title
? ui.style.LABEL_TITLE
: label.small
? ui.style.LABEL_SMALL
: ui.style.LABEL);
applyElementStyle(
element,
[
label.labelStyle,
label.style,
{

@@ -80,2 +88,6 @@ width: label.width,

textColor: label.color,
opacity:
label.dim === true ? 0.5 : label.dim === false ? 1 : label.dim,
lineBreakMode: label.wrap ? "pre-wrap" : undefined,
userSelect: label.selectable || undefined,
},

@@ -91,4 +103,4 @@ ],

element.text = String(this.observed.text);
element.icon = String(this.observed.icon);
element.icon = String(this.observed.icon || "");
}
}

@@ -5,3 +5,3 @@ import {

UITextField,
UITextFieldStyle,
ui,
} from "@desk-framework/frame-core";

@@ -26,3 +26,3 @@ import { TestOutputElement } from "../app/TestOutputElement.js";

"width",
"textFieldStyle",
"style",
);

@@ -45,3 +45,3 @@ }

case "width":
case "textFieldStyle":
case "style":
this.scheduleUpdate(undefined, this.element);

@@ -85,7 +85,7 @@ return;

element.styleClass =
getBaseStyleClass(textField.textFieldStyle) || UITextFieldStyle;
getBaseStyleClass(textField.style) || ui.style.TEXTFIELD;
applyElementStyle(
element,
[
textField.textFieldStyle,
textField.style,
textField.width !== undefined

@@ -92,0 +92,0 @@ ? { width: textField.width, minWidth: 0 }

@@ -5,3 +5,3 @@ import {

UIToggle,
UIToggleStyle,
ui,
} from "@desk-framework/frame-core";

@@ -24,3 +24,3 @@ import { TestOutputElement } from "../app/TestOutputElement.js";

"disabled",
"toggleStyle",
"style",
"labelStyle",

@@ -42,3 +42,3 @@ );

case "disabled":
case "toggleStyle":
case "style":
case "labelStyle":

@@ -81,5 +81,4 @@ this.scheduleUpdate(undefined, this.element);

// set styles
element.styleClass =
getBaseStyleClass(toggle.toggleStyle) || UIToggleStyle;
applyElementStyle(element, [toggle.toggleStyle], toggle.position);
element.styleClass = getBaseStyleClass(toggle.style) || ui.style.TOGGLE;
applyElementStyle(element, [toggle.style], toggle.position);
}

@@ -86,0 +85,0 @@ }

@@ -5,30 +5,35 @@ import { UIColor } from "@desk-framework/frame-core";

export const colors: [name: string, color: UIColor][] = [
["black", new UIColor("#000000")],
["darkerGray", new UIColor("#333333")],
["darkGray", new UIColor("#777777")],
["gray", new UIColor("#aaaaaa")],
["lightGray", new UIColor("#dddddd")],
["white", new UIColor("#ffffff")],
["slate", new UIColor("#667788")],
["lightSlate", new UIColor("#c0c8d0")],
["red", new UIColor("#ee3333")],
["orange", new UIColor("#ee9922")],
["yellow", new UIColor("#ddcc33")],
["lime", new UIColor("#99bb33")],
["green", new UIColor("#44aa44")],
["turquoise", new UIColor("#33aaaa")],
["cyan", new UIColor("#33bbbb")],
["blue", new UIColor("#2277ff")],
["violet", new UIColor("#8844ee")],
["purple", new UIColor("#aa4488")],
["magenta", new UIColor("#dd2299")],
["primary", new UIColor("@blue")],
["primaryBackground", new UIColor("@blue")],
["accent", new UIColor("@purple")],
["pageBackground", new UIColor("@background")],
["background", new UIColor("@white")],
["text", new UIColor("@background").text()],
["separator", new UIColor("@background").text().alpha(0.1)],
["controlBase", new UIColor("@background").contrast(-0.1)],
["modalShade", new UIColor("@black")],
["Black", new UIColor("#000000")],
["DarkerGray", new UIColor("#333333")],
["DarkGray", new UIColor("#777777")],
["Gray", new UIColor("#aaaaaa")],
["LightGray", new UIColor("#dddddd")],
["White", new UIColor("#ffffff")],
["Slate", new UIColor("#667788")],
["LightSlate", new UIColor("#c0c8d0")],
["Red", new UIColor("#ee3333")],
["Orange", new UIColor("#ee9922")],
["Yellow", new UIColor("#ddcc33")],
["Lime", new UIColor("#99bb33")],
["Green", new UIColor("#44aa44")],
["Turquoise", new UIColor("#33aaaa")],
["Cyan", new UIColor("#33bbbb")],
["Blue", new UIColor("#2277ff")],
["Violet", new UIColor("#8844ee")],
["Purple", new UIColor("#aa4488")],
["Magenta", new UIColor("#dd2299")],
["Separator", new UIColor("Background").text().alpha(0.1)],
["ControlBase", new UIColor("Background").contrast(-0.1)],
["Background", new UIColor("White")],
["Text", new UIColor("Background").text()],
["Danger", new UIColor("Red")],
["DangerBackground", new UIColor("Danger")],
["Success", new UIColor("Green")],
["SuccessBackground", new UIColor("Success")],
["Primary", new UIColor("Blue")],
["PrimaryBackground", new UIColor("Primary")],
["Accent", new UIColor("Purple")],
["AccentBackground", new UIColor("Accent")],
["Brand", new UIColor("Primary")],
["BrandBackground", new UIColor("Brand")],
];

@@ -5,63 +5,19 @@ import { UIIconResource } from "@desk-framework/frame-core";

export const icons: [name: string, icon: UIIconResource][] = [
["blank", new UIIconResource(`<svg viewBox="0 0 48 48"></svg>`)],
["Blank", new UIIconResource(`<svg></svg>`)],
["Close", new UIIconResource(`<svg id="test-close"></svg>`)],
["Check", new UIIconResource(`<svg id="test-check"></svg>`)],
["ChevronDown", new UIIconResource(`<svg id="test-chevronDown"></svg>`)],
["ChevronUp", new UIIconResource(`<svg id="test-chevronUp"></svg>`)],
[
"close",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M39 12l-3-3l-12 12l-12-12l-3 3l12 12l-12 12l3 3l12-12l12 12l3-3l-12-12l12-12z"/></svg>`,
),
"ChevronNext",
new UIIconResource(`<svg id="test-chevronNext"></svg>`).setMirrorRTL(),
],
[
"check",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M18 32l-8-8l-3 3L18 38l24-24-3-3z"/></svg>`,
),
"ChevronBack",
new UIIconResource(`<svg id="test-chevronBack"></svg>`).setMirrorRTL(),
],
[
"chevronDown",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M32 18l-9 9l-9-9l-3 3l12 12l12-12z"/></svg>`,
),
],
[
"chevronUp",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M32 30l-9-9l-9 9l-3-3l12-12l12 12z"/></svg>`,
),
],
[
"chevronNext",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M18 32l9-9l-9-9l3-3l12 12l-12 12z"/></svg>`,
).setMirrorRTL(),
],
[
"chevronBack",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M30 32l-9-9l9-9l-3-3l-12 12l12 12z"/></svg>`,
).setMirrorRTL(),
],
[
"plus",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M8 20h14V6h4v14h14v4H26v14h-4V24H8z"/></svg>`,
),
],
[
"minus",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M8 20H40v4H8z"/></svg>`,
),
],
[
"menu",
new UIIconResource(
`<svg viewBox="0 0 48 48"><path d="M8 10H40v4H8zM8 20H40v4H8zM8 30H40v4H8z"/></svg>`,
),
],
[
"more",
new UIIconResource(
`<svg viewBox="0 0 48 48"><circle cx="24"cy="10"r="4"/><circle cx="24"cy="24"r="4"/><circle cx="24"cy="38"r="4"/></svg>`,
),
],
["Plus", new UIIconResource(`<svg id="test-plus"></svg>`)],
["Minus", new UIIconResource(`<svg id="test-minus"></svg>`)],
["Menu", new UIIconResource(`<svg id="test-menu"></svg>`)],
["More", new UIIconResource(`<svg id="test-more"></svg>`)],
];
import {
RenderContext,
UICell,
UITheme,
UIViewRenderer,
View,

@@ -10,2 +8,3 @@ ViewComposite,

bound,
ui,
} from "@desk-framework/frame-core";

@@ -23,4 +22,4 @@

protected override createView() {
return new (UICell.with(
UIViewRenderer.with({
return new (ui.cell(
ui.renderView({
view: bound("dialogView"),

@@ -27,0 +26,0 @@ onViewUnlinked: "DialogViewUnlinked",

@@ -7,5 +7,3 @@ import {

StringConvertible,
UIButton,
UICell,
UILabel,
ui,
UITheme,

@@ -55,6 +53,6 @@ ViewComposite,

protected override createView() {
return new (UICell.with(
return new (ui.column(
{ accessibleRole: "alertdialog" },
...this.options.messages.map((text) => UILabel.withText(text)),
UIButton.with({
...this.options.messages.map((text) => ui.label(String(text))),
ui.button({
label: this.confirmLabel,

@@ -64,3 +62,3 @@ onClick: "+Confirm",

}),
UIButton.with({
ui.button({
hidden: !this.otherLabel,

@@ -70,3 +68,3 @@ label: this.otherLabel,

}),
UIButton.with({
ui.button({
hidden: !this.cancelLabel,

@@ -73,0 +71,0 @@ label: this.cancelLabel,

@@ -5,3 +5,6 @@ import { app } from "@desk-framework/frame-core";

import { OutputAssertion, OutputSelectFilter } from "./app/OutputAssertion.js";
import { TestRenderer } from "./renderer/TestRenderer.js";
import {
RenderedTestMessageDialog,
TestRenderer,
} from "./renderer/TestRenderer.js";
import { val2str } from "./log.js";

@@ -332,8 +335,9 @@

/**
* Waits for the global navigation path to match the given string
* Waits for the global navigation location to match the given page ID and detail
*
* @summary This method starts checking the navigation path periodically (using {@link GlobalContext.getPath()}), and waits for the path to match the provided string. If the path still doesn't match after the given timeout (number of milliseconds) this method throws an error.
* @summary This method starts checking the navigation controller periodically, and waits for the path to match the provided string. If the path still doesn't match after the given timeout (number of milliseconds) this method throws an error.
* @note This method is asynchronous and **must** be `await`-ed.
* @param timeout Timeout, in milliseconds
* @param path Path to wait for, must be an exact match
* @param pageId Page ID to wait for, must be an exact match
* @param detail Detail string to wait for (defaults to empty string), must be an exact match
* @returns A promise (void) that's resolved when the patch matches, or rejected when a timeout occurs.

@@ -343,11 +347,13 @@ *

* describe("My scope", () => {
* test("Wait for path", async (t) => {
* test("Wait for navigation", async (t) => {
* // ... navigate to a path somehow
* await t.expectPathAsync(100, "foo/bar");
* await t.expectNavAsync(100, "foo");
* });
* });
*/
async expectPathAsync(timeout: number, path: string) {
async expectNavAsync(timeout: number, pageId: string, detail = "") {
await this.pollAsync(
() => app.getPath() === path,
() =>
app.activities.navigationController.pageId === pageId &&
app.activities.navigationController.detail === detail,
5,

@@ -357,3 +363,10 @@ timeout,

Error(
`Expected path ${val2str(path)} but it is ${val2str(app.getPath())}`,
"Expected navigation to " +
val2str(pageId + "/" + detail) +
" but location is " +
val2str(
app.activities.navigationController.pageId +
"/" +
app.activities.navigationController.detail,
),
),

@@ -398,6 +411,27 @@ );

/**
* Waits for an alert or confirm dialog to be rendered (by the test renderer)
* - This method uses {@link TestRenderer.expectMessageDialogAsync()}, refer to its documentation for details.
* - This method is asynchronous and **must** be `await`-ed.
* @param timeout Timeout, in milliseconds
* @param match A list of strings or regular expressions to match the dialog message
* @returns A promise that's resolved to a {@link RenderedTestMessageDialog} instance for checking content or pressing buttons, or rejected when a timeout occurs.
*
* @example
* describe("My scope", () => {
* test("Cancel confirm dialog", async (t) => {
* // ...
* let p = app.showConfirmDialog("Are you sure?");
* await (
* await t.expectMessageDialogAsync(100, /sure/)
* ).cancelAsync();
* let result = await p;
* expect(result).toBe(false);
* });
* });
*/
async expectMessageDialogAsync(
timeout: number,
...match: Array<string | RegExp>
) {
): Promise<RenderedTestMessageDialog> {
if (!(app.renderer instanceof TestRenderer)) {

@@ -404,0 +438,0 @@ throw Error("Test renderer not found, run `useTestContext()` first");

import {
app,
bound,
Activity,
StringConvertible,
UIButton,
UICell,
UILabel,
UITextField,
ViewComposite,
ViewEvent,
app,
bound,
ui,
} from "@desk-framework/frame-core";

@@ -18,5 +16,5 @@ import { describe, expect, test, useTestContext } from "../../dist/index.js";

protected override ready() {
this.view = new (UICell.with(
UITextField.with({ value: bound("count"), onInput: "SetCount" }),
UIButton.withLabel("+", "CountUp"),
this.view = new (ui.cell(
ui.textField({ value: bound("count"), onInput: "SetCount" }),
ui.button("+", "CountUp"),
))();

@@ -41,3 +39,3 @@ app.showPage(this.view);

useTestContext((options) => {
options.path = "count";
options.navigationPageId = "count";
});

@@ -49,7 +47,8 @@ activity = new CountActivity();

test("Single view is rendered", async (t) => {
const MyView = ViewComposite.define<{ title?: StringConvertible }>((p) =>
UILabel.withText(p.title),
).with({ title: "TEST" });
let view = new MyView();
app.showPage(view);
const MyView = ViewComposite.withPreset(
{ title: StringConvertible.EMPTY },
ui.label(bound.string("title")),
).preset({ title: "TEST" });
let myView = new MyView();
app.showPage(myView);
await t.expectOutputAsync(100, { text: "TEST" });

@@ -64,7 +63,7 @@ });

// initial path should be set directly
expect(app.getPath()).toBe("count");
expect(app.activities.navigationController.pageId).toBe("count");
// setting another path takes some time
app.navigate("/another/path");
await t.expectPathAsync(100, "another/path");
app.navigate("/another/path/here");
await t.expectNavAsync(100, "another", "path/here");

@@ -71,0 +70,0 @@ // by then, the activity should be made inactive

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc