mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-07 02:50:34 +00:00

Sadly, ts-node doesn't work with the newer typescript anymore. As per https://github.com/avajs/ava/blob/main/docs/recipes/typescript.md , this patch changes from the second to the first method: build the tests first, then run them with ava. This requires a few small changes in the tests, as import.meta.url now returns the transpiled .mjs file in the build dir, instead of the source dir, as expected by the test.
1038 lines
28 KiB
TypeScript
1038 lines
28 KiB
TypeScript
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
import test from "ava";
|
|
import * as path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { Jimp } from "jimp";
|
|
import { captureStderr } from "capture-console";
|
|
|
|
import {
|
|
private_api,
|
|
type ImageData,
|
|
ArrayModel,
|
|
type Model,
|
|
} from "../dist/index.js";
|
|
|
|
const filename = fileURLToPath(import.meta.url).replace("build", "__test__");
|
|
const dirname = path.dirname(filename);
|
|
|
|
test("get/set string properties", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`export component App { in-out property <string> name: "Initial"; }`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
t.is(instance!.getProperty("name"), "Initial");
|
|
|
|
instance!.setProperty("name", "Hello");
|
|
t.is(instance!.getProperty("name"), "Hello");
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("name", 42);
|
|
},
|
|
{
|
|
code: "InvalidArg",
|
|
message: "expect String, got: Number",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("name", { blah: "foo" });
|
|
},
|
|
{
|
|
code: "InvalidArg",
|
|
message: "expect String, got: Object",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("get/set number properties", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
in-out property <float> age: 42;
|
|
}`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
t.is(instance!.getProperty("age"), 42);
|
|
|
|
instance!.setProperty("age", 100);
|
|
t.is(instance!.getProperty("age"), 100);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("age", "Hello");
|
|
},
|
|
{
|
|
code: "InvalidArg",
|
|
message: "expect Number, got: String",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("age", { blah: "foo" });
|
|
},
|
|
{
|
|
code: "InvalidArg",
|
|
message: "expect Number, got: Object",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("get/set bool properties", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`export component App { in-out property <bool> ready: true; }`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
t.is(instance!.getProperty("ready"), true);
|
|
|
|
instance!.setProperty("ready", false);
|
|
t.is(instance!.getProperty("ready"), false);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("ready", "Hello");
|
|
},
|
|
{
|
|
code: "InvalidArg",
|
|
message: "expect Boolean, got: String",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("ready", { blah: "foo" });
|
|
},
|
|
{
|
|
code: "InvalidArg",
|
|
message: "expect Boolean, got: Object",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("set struct properties", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export struct Player {
|
|
name: string,
|
|
age: int,
|
|
energy_level: float
|
|
}
|
|
export component App {
|
|
in-out property <Player> player: {
|
|
name: "Florian",
|
|
age: 20,
|
|
energy_level: 40%
|
|
};
|
|
}
|
|
`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
t.deepEqual(instance!.getProperty("player"), {
|
|
name: "Florian",
|
|
age: 20,
|
|
energy_level: 0.4,
|
|
});
|
|
|
|
instance!.setProperty("player", {
|
|
name: "Simon",
|
|
age: 22,
|
|
energy_level: 0.8,
|
|
});
|
|
|
|
t.deepEqual(instance!.getProperty("player"), {
|
|
name: "Simon",
|
|
age: 22,
|
|
energy_level: 0.8,
|
|
});
|
|
|
|
// Extra properties are thrown away
|
|
instance!.setProperty("player", {
|
|
name: "Excessive Player",
|
|
age: 100,
|
|
energy_level: 0.8,
|
|
weight: 200,
|
|
});
|
|
t.deepEqual(instance!.getProperty("player"), {
|
|
name: "Excessive Player",
|
|
age: 100,
|
|
energy_level: 0.8,
|
|
});
|
|
|
|
// Missing properties are defaulted
|
|
instance!.setProperty("player", { age: 39 });
|
|
t.deepEqual(instance!.getProperty("player"), {
|
|
name: "",
|
|
age: 39,
|
|
energy_level: 0.0,
|
|
});
|
|
});
|
|
|
|
test("get/set image properties", async (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
in-out property <image> image: @image-url("resources/rgb.png");
|
|
in property <image> external-image;
|
|
out property <bool> external-image-ok: self.external-image.width == 64 && self.external-image.height == 64;
|
|
}`,
|
|
filename,
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
const slintImage = instance!.getProperty("image");
|
|
if (t.true(slintImage instanceof private_api.SlintImageData)) {
|
|
t.deepEqual((slintImage as private_api.SlintImageData).width, 64);
|
|
t.deepEqual((slintImage as private_api.SlintImageData).height, 64);
|
|
t.true((slintImage as ImageData).path.endsWith("rgb.png"));
|
|
|
|
const image = await Jimp.read(path.join(dirname, "resources/rgb.png"));
|
|
|
|
// Sanity check: setProperty fails when passed definitely a non-image
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("external-image", 42);
|
|
},
|
|
{
|
|
message:
|
|
"Cannot convert object to image, because the provided object does not have an u32 `width` property",
|
|
},
|
|
);
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("external-image", { garbage: true });
|
|
},
|
|
{
|
|
message:
|
|
"Cannot convert object to image, because the provided object does not have an u32 `width` property",
|
|
},
|
|
);
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("external-image", { width: [1, 2, 3] });
|
|
},
|
|
{
|
|
message:
|
|
"Cannot convert object to image, because the provided object does not have an u32 `height` property",
|
|
},
|
|
);
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("external-image", {
|
|
width: 1,
|
|
height: 1,
|
|
data: new Uint8ClampedArray(1),
|
|
});
|
|
},
|
|
{
|
|
message:
|
|
"data property does not have the correct size; expected 1 (width) * 1 (height) * 4 = 1; got 4",
|
|
},
|
|
);
|
|
|
|
t.is(image.bitmap.width, 64);
|
|
t.is(image.bitmap.height, 64);
|
|
// Duck typing: The `image.bitmap` object that Jump returns, has the shape of the official ImageData, so
|
|
// it should be possible to use it with Slint:
|
|
instance!.setProperty("external-image", image.bitmap);
|
|
t.is(instance!.getProperty("external-image-ok"), true);
|
|
|
|
t.is(image.bitmap.data.length, (slintImage as ImageData).data.length);
|
|
t.deepEqual(image.bitmap.data, (slintImage as ImageData).data);
|
|
|
|
t.deepEqual(
|
|
(instance!.getProperty("external-image") as ImageData).path,
|
|
undefined,
|
|
);
|
|
}
|
|
});
|
|
|
|
test("get/set brush properties", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
in-out property <brush> black: #000000;
|
|
in-out property <brush> trans: transparent;
|
|
in-out property <brush> ref: transparent;
|
|
in-out property <brush> linear-gradient: @linear-gradient(90deg, #3f87a6 0%, #ebf8e1 50%, #f69d3c 100%);
|
|
in-out property <brush> radial-gradient: @radial-gradient(circle, #f00 0%, #0f0 50%, #00f 100%);
|
|
in-out property <color> ref-color;
|
|
}
|
|
`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
const black = instance!.getProperty("black");
|
|
|
|
t.is((black as private_api.SlintBrush).toString(), "#000000ff");
|
|
|
|
if (t.true(black instanceof private_api.SlintBrush)) {
|
|
const blackSlintRgbaColor = (black as private_api.SlintBrush).color;
|
|
t.deepEqual(blackSlintRgbaColor.red, 0);
|
|
t.deepEqual(blackSlintRgbaColor.green, 0);
|
|
t.deepEqual(blackSlintRgbaColor.blue, 0);
|
|
}
|
|
|
|
instance?.setProperty("black", "#ffffff");
|
|
const white = instance!.getProperty("black");
|
|
|
|
if (t.true(white instanceof private_api.SlintBrush)) {
|
|
const whiteSlintRgbaColor = (white as private_api.SlintBrush).color;
|
|
t.deepEqual(whiteSlintRgbaColor.red, 255);
|
|
t.deepEqual(whiteSlintRgbaColor.green, 255);
|
|
t.deepEqual(whiteSlintRgbaColor.blue, 255);
|
|
}
|
|
|
|
const transparent = instance!.getProperty("trans");
|
|
|
|
if (t.true(black instanceof private_api.SlintBrush)) {
|
|
t.assert((transparent as private_api.SlintBrush).isTransparent);
|
|
}
|
|
|
|
const ref = new private_api.SlintBrush({
|
|
red: 100,
|
|
green: 110,
|
|
blue: 120,
|
|
alpha: 255,
|
|
});
|
|
instance!.setProperty("ref", ref);
|
|
|
|
let instance_ref = instance!.getProperty("ref");
|
|
|
|
if (t.true(instance_ref instanceof private_api.SlintBrush)) {
|
|
const ref_color = (instance_ref as private_api.SlintBrush).color;
|
|
t.deepEqual(ref_color.red, 100);
|
|
t.deepEqual(ref_color.green, 110);
|
|
t.deepEqual(ref_color.blue, 120);
|
|
t.deepEqual(ref_color.alpha, 255);
|
|
}
|
|
|
|
instance!.setProperty("ref", {
|
|
color: { red: 110, green: 120, blue: 125, alpha: 255 },
|
|
});
|
|
|
|
instance_ref = instance!.getProperty("ref");
|
|
|
|
if (t.true(instance_ref instanceof private_api.SlintBrush)) {
|
|
const ref_color = (instance_ref as private_api.SlintBrush).color;
|
|
t.deepEqual(ref_color.red, 110);
|
|
t.deepEqual(ref_color.green, 120);
|
|
t.deepEqual(ref_color.blue, 125);
|
|
t.deepEqual(ref_color.alpha, 255);
|
|
}
|
|
|
|
instance!.setProperty("ref", {
|
|
red: 110,
|
|
green: 120,
|
|
blue: 125,
|
|
alpha: 255,
|
|
});
|
|
|
|
instance_ref = instance!.getProperty("ref");
|
|
|
|
if (t.true(instance_ref instanceof private_api.SlintBrush)) {
|
|
const ref_color = (instance_ref as private_api.SlintBrush).color;
|
|
t.deepEqual(ref_color.red, 110);
|
|
t.deepEqual(ref_color.green, 120);
|
|
t.deepEqual(ref_color.blue, 125);
|
|
t.deepEqual(ref_color.alpha, 255);
|
|
}
|
|
|
|
instance!.setProperty("ref", {});
|
|
|
|
instance_ref = instance!.getProperty("ref");
|
|
|
|
if (t.true(instance_ref instanceof private_api.SlintBrush)) {
|
|
const ref_color = (instance_ref as private_api.SlintBrush).color;
|
|
t.deepEqual(ref_color.red, 0);
|
|
t.deepEqual(ref_color.green, 0);
|
|
t.deepEqual(ref_color.blue, 0);
|
|
t.deepEqual(ref_color.alpha, 0);
|
|
}
|
|
|
|
const radialGradient = instance!.getProperty("radial-gradient");
|
|
|
|
if (t.true(radialGradient instanceof private_api.SlintBrush)) {
|
|
t.is(
|
|
(radialGradient as private_api.SlintBrush).toString(),
|
|
"radial-gradient(circle, rgba(255, 0, 0, 255) 0%, rgba(0, 255, 0, 255) 50%, rgba(0, 0, 255, 255) 100%)",
|
|
);
|
|
}
|
|
|
|
const linearGradient = instance!.getProperty("linear-gradient");
|
|
|
|
if (t.true(linearGradient instanceof private_api.SlintBrush)) {
|
|
t.is(
|
|
(linearGradient as private_api.SlintBrush).toString(),
|
|
"linear-gradient(90deg, rgba(63, 135, 166, 255) 0%, rgba(235, 248, 225, 255) 50%, rgba(246, 157, 60, 255) 100%)",
|
|
);
|
|
}
|
|
|
|
t.throws(
|
|
() => {
|
|
instance.setProperty("ref-color", {
|
|
red: "abc",
|
|
blue: 0,
|
|
green: 0,
|
|
alpha: 0,
|
|
});
|
|
},
|
|
{
|
|
code: "NumberExpected",
|
|
message: "Failed to convert napi value String into rust type `f64`",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance.setProperty("ref-color", {
|
|
red: 0,
|
|
blue: true,
|
|
green: 0,
|
|
alpha: 0,
|
|
});
|
|
},
|
|
{
|
|
code: "NumberExpected",
|
|
message:
|
|
"Failed to convert napi value Boolean into rust type `f64`",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance.setProperty("ref-color", {
|
|
red: 0,
|
|
blue: 0,
|
|
green: true,
|
|
alpha: 0,
|
|
});
|
|
},
|
|
{
|
|
code: "NumberExpected",
|
|
message:
|
|
"Failed to convert napi value Boolean into rust type `f64`",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance.setProperty("ref-color", {
|
|
red: 0,
|
|
blue: 0,
|
|
green: 0,
|
|
alpha: new private_api.SlintRgbaColor(),
|
|
});
|
|
},
|
|
{
|
|
code: "NumberExpected",
|
|
message: "Failed to convert napi value Object into rust type `f64`",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance.setProperty("ref-color", { blue: 0, green: 0, alpha: 0 });
|
|
},
|
|
{
|
|
code: "GenericFailure",
|
|
message: "Property red is missing",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance.setProperty("ref-color", { red: 0, green: 0, alpha: 0 });
|
|
},
|
|
{
|
|
code: "GenericFailure",
|
|
message: "Property blue is missing",
|
|
},
|
|
);
|
|
|
|
instance.setProperty("ref-color", { red: 0, green: 0, blue: 0 });
|
|
instance_ref = instance!.getProperty("ref-color");
|
|
|
|
if (t.true(instance_ref instanceof private_api.SlintBrush)) {
|
|
const ref_color = (instance_ref as private_api.SlintBrush).color;
|
|
t.deepEqual(ref_color.red, 0);
|
|
t.deepEqual(ref_color.green, 0);
|
|
t.deepEqual(ref_color.blue, 0);
|
|
t.deepEqual(ref_color.alpha, 255);
|
|
}
|
|
});
|
|
|
|
test("get/set enum properties", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`export enum Direction { up, down }
|
|
export component App { in-out property <Direction> direction: up; }`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
t.is(instance!.getProperty("direction"), "up");
|
|
|
|
instance!.setProperty("direction", "down");
|
|
t.is(instance!.getProperty("direction"), "down");
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("direction", 42);
|
|
},
|
|
{
|
|
code: "InvalidArg",
|
|
message: "expect String, got: Number",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("direction", { blah: "foo" });
|
|
},
|
|
{
|
|
code: "InvalidArg",
|
|
message: "expect String, got: Object",
|
|
},
|
|
);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("direction", "left");
|
|
},
|
|
{
|
|
code: "GenericFailure",
|
|
message: "left is not a value of enum Direction",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("ArrayModel", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export struct Player {
|
|
name: string,
|
|
age: int
|
|
}
|
|
|
|
export component App {
|
|
in-out property <[int]> int-model;
|
|
in-out property <[string]> string-model;
|
|
in-out property <[Player]> struct-model;
|
|
}`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
t.deepEqual(Array.from(new ArrayModel([3, 2, 1])), [3, 2, 1]);
|
|
|
|
instance!.setProperty("int-model", new ArrayModel([10, 9, 8]));
|
|
|
|
const intArrayModel = instance!.getProperty(
|
|
"int-model",
|
|
) as ArrayModel<number>;
|
|
t.deepEqual(intArrayModel.rowCount(), 3);
|
|
t.deepEqual(intArrayModel.values(), new ArrayModel([10, 9, 8]).values());
|
|
|
|
instance!.setProperty(
|
|
"string-model",
|
|
new ArrayModel(["Simon", "Olivier", "Auri", "Tobias", "Florian"]),
|
|
);
|
|
|
|
const stringArrayModel = instance!.getProperty(
|
|
"string-model",
|
|
) as ArrayModel<number>;
|
|
t.deepEqual(
|
|
stringArrayModel.values(),
|
|
new ArrayModel([
|
|
"Simon",
|
|
"Olivier",
|
|
"Auri",
|
|
"Tobias",
|
|
"Florian",
|
|
]).values(),
|
|
);
|
|
|
|
instance!.setProperty(
|
|
"struct-model",
|
|
new ArrayModel([
|
|
{ name: "simon", age: 22 },
|
|
{ name: "florian", age: 22 },
|
|
]),
|
|
);
|
|
|
|
const structArrayModel = instance!.getProperty(
|
|
"struct-model",
|
|
) as ArrayModel<object>;
|
|
t.deepEqual(
|
|
structArrayModel.values(),
|
|
new ArrayModel([
|
|
{ name: "simon", age: 22 },
|
|
{ name: "florian", age: 22 },
|
|
]).values(),
|
|
);
|
|
});
|
|
|
|
test("MapModel", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
in-out property <[string]> model;
|
|
}`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
interface Name {
|
|
first: string;
|
|
last: string;
|
|
}
|
|
|
|
const nameModel: ArrayModel<Name> = new ArrayModel([
|
|
{ first: "Hans", last: "Emil" },
|
|
{ first: "Max", last: "Mustermann" },
|
|
{ first: "Roman", last: "Tisch" },
|
|
]);
|
|
|
|
const mapModel = new private_api.MapModel(nameModel, (data) => {
|
|
return data.last + ", " + data.first;
|
|
});
|
|
|
|
instance!.setProperty("model", mapModel);
|
|
|
|
nameModel.setRowData(0, { first: "Simon", last: "Hausmann" });
|
|
nameModel.setRowData(1, { first: "Olivier", last: "Goffart" });
|
|
|
|
const checkModel = instance!.getProperty("model") as Model<string>;
|
|
t.is(checkModel.rowData(0), "Hausmann, Simon");
|
|
t.is(checkModel.rowData(1), "Goffart, Olivier");
|
|
t.is(checkModel.rowData(2), "Tisch, Roman");
|
|
});
|
|
|
|
test("MapModel undefined rowData sourcemodel", (t) => {
|
|
const nameModel: ArrayModel<number> = new ArrayModel([1, 2, 3]);
|
|
|
|
let mapFunctionCallCount = 0;
|
|
const mapModel = new private_api.MapModel<number, string>(
|
|
nameModel,
|
|
(data) => {
|
|
mapFunctionCallCount++;
|
|
return data.toString();
|
|
},
|
|
);
|
|
|
|
for (let i = 0; i < mapModel.rowCount(); ++i) {
|
|
mapModel.rowData(i);
|
|
}
|
|
t.deepEqual(mapFunctionCallCount, mapModel.rowCount());
|
|
mapFunctionCallCount = 0;
|
|
t.is(nameModel.rowData(nameModel.rowCount()), undefined);
|
|
t.deepEqual(mapFunctionCallCount, 0);
|
|
t.is(mapModel.rowData(mapModel.rowCount()), undefined);
|
|
t.deepEqual(mapFunctionCallCount, 0);
|
|
});
|
|
|
|
test("ArrayModel rowCount", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
out property <int> model-length: model.length;
|
|
in-out property <[int]> model;
|
|
}`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
const model = new ArrayModel([10, 9, 8]);
|
|
|
|
instance!.setProperty("model", model);
|
|
t.is(3, model.rowCount());
|
|
t.is(3, instance?.getProperty("model-length") as number);
|
|
});
|
|
|
|
test("ArrayModel rowData/setRowData", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
callback data(int) -> int;
|
|
|
|
in-out property <[int]> model;
|
|
|
|
data(row) => {
|
|
model[row]
|
|
}
|
|
}`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
const model = new ArrayModel([10, 9, 8]);
|
|
|
|
instance!.setProperty("model", model);
|
|
t.is(9, model.rowData(1));
|
|
t.deepEqual(instance!.invoke("data", [1]), 9);
|
|
|
|
model.setRowData(1, 4);
|
|
t.is(4, model.rowData(1));
|
|
t.deepEqual(instance!.invoke("data", [1]), 4);
|
|
});
|
|
|
|
test("Model notify", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
width: 300px;
|
|
height: 300px;
|
|
|
|
out property<length> layout-height: layout.height;
|
|
in-out property<[length]> fixed-height-model;
|
|
|
|
VerticalLayout {
|
|
alignment: start;
|
|
|
|
layout := VerticalLayout {
|
|
for fixed-height in fixed-height-model: Rectangle {
|
|
background: blue;
|
|
height: fixed-height;
|
|
}
|
|
}
|
|
}
|
|
|
|
}`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
const model = new ArrayModel([100, 0]);
|
|
|
|
instance!.setProperty("fixed-height-model", model);
|
|
t.is(100, instance!.getProperty("layout-height") as number);
|
|
model.setRowData(1, 50);
|
|
t.is(150, instance!.getProperty("layout-height") as number);
|
|
model.push(75);
|
|
t.is(225, instance!.getProperty("layout-height") as number);
|
|
model.remove(1, 2);
|
|
t.is(100, instance!.getProperty("layout-height") as number);
|
|
});
|
|
|
|
test("model from array", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
in-out property <[int]> int-array;
|
|
in-out property <[string]> string-array;
|
|
}`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
instance!.setProperty("int-array", [10, 9, 8]);
|
|
const wrapped_int_model = instance!.getProperty(
|
|
"int-array",
|
|
) as Model<number>;
|
|
t.deepEqual(Array.from(wrapped_int_model), [10, 9, 8]);
|
|
t.deepEqual(wrapped_int_model.rowCount(), 3);
|
|
t.deepEqual(wrapped_int_model.rowData(0), 10);
|
|
t.deepEqual(wrapped_int_model.rowData(1), 9);
|
|
t.deepEqual(wrapped_int_model.rowData(2), 8);
|
|
t.deepEqual(Array.from(wrapped_int_model), [10, 9, 8]);
|
|
|
|
instance!.setProperty("string-array", [
|
|
"Simon",
|
|
"Olivier",
|
|
"Auri",
|
|
"Tobias",
|
|
"Florian",
|
|
]);
|
|
const wrapped_string_model = instance!.getProperty(
|
|
"string-array",
|
|
) as Model<string>;
|
|
t.deepEqual(wrapped_string_model.rowCount(), 5);
|
|
t.deepEqual(wrapped_string_model.rowData(0), "Simon");
|
|
t.deepEqual(wrapped_string_model.rowData(1), "Olivier");
|
|
t.deepEqual(wrapped_string_model.rowData(2), "Auri");
|
|
t.deepEqual(wrapped_string_model.rowData(3), "Tobias");
|
|
t.deepEqual(wrapped_string_model.rowData(4), "Florian");
|
|
});
|
|
|
|
test("invoke callback", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export struct Person {
|
|
name: string
|
|
}
|
|
export component App {
|
|
callback great(string, string, string, string, string);
|
|
callback great-person(Person);
|
|
callback person() -> Person;
|
|
callback get-string() -> string;
|
|
|
|
person => {
|
|
{
|
|
name: "florian"
|
|
}
|
|
}
|
|
|
|
get-string => {
|
|
"string"
|
|
}
|
|
}
|
|
`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
let speakTest: string;
|
|
|
|
instance!.setCallback(
|
|
"great",
|
|
(a: string, b: string, c: string, d: string, e: string) => {
|
|
speakTest =
|
|
"hello " + a + ", " + b + ", " + c + ", " + d + " and " + e;
|
|
},
|
|
);
|
|
|
|
instance!.invoke("great", [
|
|
"simon",
|
|
"olivier",
|
|
"auri",
|
|
"tobias",
|
|
"florian",
|
|
]);
|
|
t.deepEqual(speakTest, "hello simon, olivier, auri, tobias and florian");
|
|
|
|
instance!.setCallback("great-person", (p: any) => {
|
|
speakTest = "hello " + p.name;
|
|
});
|
|
|
|
instance!.invoke("great-person", [{ name: "simon" }]);
|
|
t.deepEqual(speakTest, "hello simon");
|
|
|
|
instance!.invoke("great-person", [{ hello: "simon" }]);
|
|
t.deepEqual(speakTest, "hello ");
|
|
|
|
t.deepEqual(instance!.invoke("get-string", []), "string");
|
|
t.deepEqual(instance!.invoke("person", []), { name: "florian" });
|
|
});
|
|
|
|
test("wrong callback return type ", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export struct Person {
|
|
name: string,
|
|
age: int,
|
|
|
|
}
|
|
export component App {
|
|
callback get-string() -> string;
|
|
callback get-int() -> int;
|
|
callback get-bool() -> bool;
|
|
callback get-person() -> Person;
|
|
}
|
|
`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
let speakTest: string;
|
|
|
|
instance!.setCallback("get-string", () => {
|
|
return 20;
|
|
});
|
|
|
|
const string = instance!.invoke("get-string", []);
|
|
t.deepEqual(string, "");
|
|
|
|
instance!.setCallback("get-int", () => {
|
|
return "string";
|
|
});
|
|
|
|
const int = instance!.invoke("get-int", []);
|
|
t.deepEqual(int, 0);
|
|
|
|
instance!.setCallback("get-bool", () => {
|
|
return "string";
|
|
});
|
|
|
|
const bool = instance!.invoke("get-bool", []);
|
|
t.deepEqual(bool, false);
|
|
|
|
instance!.setCallback("get-person", () => {
|
|
return "string";
|
|
});
|
|
|
|
const person = instance!.invoke("get-person", []);
|
|
t.deepEqual(person, { name: "", age: 0 });
|
|
});
|
|
|
|
test("wrong global callback return type ", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export struct Person {
|
|
name: string,
|
|
age: int,
|
|
}
|
|
export global Global {
|
|
callback get-string() -> string;
|
|
callback get-int() -> int;
|
|
callback get-bool() -> bool;
|
|
callback get-person() -> Person;
|
|
}
|
|
export component App {
|
|
}
|
|
`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
let speakTest: string;
|
|
|
|
instance!.setGlobalCallback("Global", "get-string", () => {
|
|
return 20;
|
|
});
|
|
|
|
const string = instance!.invokeGlobal("Global", "get-string", []);
|
|
t.deepEqual(string, "");
|
|
|
|
instance!.setGlobalCallback("Global", "get-bool", () => {
|
|
return "string";
|
|
});
|
|
|
|
const bool = instance!.invokeGlobal("Global", "get-bool", []);
|
|
t.deepEqual(bool, false);
|
|
|
|
instance!.setGlobalCallback("Global", "get-person", () => {
|
|
return "string";
|
|
});
|
|
|
|
const person = instance!.invokeGlobal("Global", "get-person", []);
|
|
t.deepEqual(person, { name: "", age: 0 });
|
|
});
|
|
|
|
test("throw exception in callback", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
callback throw-something();
|
|
}
|
|
`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
let speakTest: string;
|
|
|
|
instance!.setCallback("throw-something", () => {
|
|
throw new Error("I'm an error");
|
|
});
|
|
|
|
const output = captureStderr(() => {
|
|
instance!.invoke("throw-something", []);
|
|
});
|
|
t.assert(
|
|
output.includes("Node.js: Invoking callback 'throw-something' failed:"),
|
|
`Output was ${output}`,
|
|
);
|
|
t.assert(output.includes("I'm an error"), `Output was ${output}`);
|
|
});
|
|
|
|
test("throw exception set color", (t) => {
|
|
const compiler = new private_api.ComponentCompiler();
|
|
const definition = compiler.buildFromSource(
|
|
`
|
|
export component App {
|
|
in-out property <color> test;
|
|
}
|
|
`,
|
|
"",
|
|
);
|
|
t.not(definition.App, null);
|
|
|
|
const instance = definition.App!.create();
|
|
t.not(instance, null);
|
|
|
|
t.throws(
|
|
() => {
|
|
instance!.setProperty("test", { garbage: true });
|
|
},
|
|
{
|
|
code: "GenericFailure",
|
|
message: "Property red is missing",
|
|
},
|
|
);
|
|
});
|