Fix: aiscriptディレクトリ内の型エラー解消と単体テスト (#15191)
* AiScript APIの型エラーに対処 * AiScript UI APIのテスト作成 * onInputなどがPromiseを返すように * AiScript共通APIのテスト作成 * CHANGELOG記載 * 定数のテストをconcurrentに * vi.mockを使用 * misskeyApiをmisskeyApiUntypedのエイリアスとする * 期待されるエラーメッセージを修正 * Mk:removeのテスト * misskeyApiの型を変更
This commit is contained in:
401
packages/frontend/test/aiscript/api.test.ts
Normal file
401
packages/frontend/test/aiscript/api.test.ts
Normal file
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
|
||||
import { errors, Interpreter, Parser, values } from '@syuilo/aiscript';
|
||||
import {
|
||||
afterAll,
|
||||
afterEach,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
test,
|
||||
vi
|
||||
} from 'vitest';
|
||||
|
||||
async function exe(script: string): Promise<values.Value[]> {
|
||||
const outputs: values.Value[] = [];
|
||||
const interpreter = new Interpreter(
|
||||
createAiScriptEnv({ storageKey: 'widget' }),
|
||||
{
|
||||
in: aiScriptReadline,
|
||||
out: (value) => {
|
||||
outputs.push(value);
|
||||
}
|
||||
}
|
||||
);
|
||||
const ast = Parser.parse(script);
|
||||
await interpreter.exec(ast);
|
||||
return outputs;
|
||||
}
|
||||
|
||||
let $iMock = vi.hoisted<Partial<typeof import('@/account.js').$i> | null >(
|
||||
() => null
|
||||
);
|
||||
|
||||
vi.mock('@/account.js', () => {
|
||||
return {
|
||||
get $i() {
|
||||
return $iMock;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const osMock = vi.hoisted(() => {
|
||||
return {
|
||||
inputText: vi.fn(),
|
||||
alert: vi.fn(),
|
||||
confirm: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@/os.js', () => {
|
||||
return osMock;
|
||||
});
|
||||
|
||||
const misskeyApiMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('@/scripts/misskey-api.js', () => {
|
||||
return { misskeyApi: misskeyApiMock };
|
||||
});
|
||||
|
||||
describe('AiScript common API', () => {
|
||||
afterAll(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('readline', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test.sequential('ok', async () => {
|
||||
osMock.inputText.mockImplementationOnce(async ({ title }) => {
|
||||
expect(title).toBe('question');
|
||||
return {
|
||||
canceled: false,
|
||||
result: 'Hello',
|
||||
};
|
||||
});
|
||||
const [res] = await exe(`
|
||||
<: readline('question')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.STR('Hello'));
|
||||
expect(osMock.inputText).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('cancelled', async () => {
|
||||
osMock.inputText.mockImplementationOnce(async ({ title }) => {
|
||||
expect(title).toBe('question');
|
||||
return {
|
||||
canceled: true,
|
||||
result: undefined,
|
||||
};
|
||||
});
|
||||
const [res] = await exe(`
|
||||
<: readline('question')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.STR(''));
|
||||
expect(osMock.inputText).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe('user constants', () => {
|
||||
describe.sequential('logged in', () => {
|
||||
beforeAll(() => {
|
||||
$iMock = {
|
||||
id: 'xxxxxxxx',
|
||||
name: '藍',
|
||||
username: 'ai',
|
||||
};
|
||||
});
|
||||
|
||||
test.concurrent('USER_ID', async () => {
|
||||
const [res] = await exe(`
|
||||
<: USER_ID
|
||||
`);
|
||||
expect(res).toStrictEqual(values.STR('xxxxxxxx'));
|
||||
});
|
||||
|
||||
test.concurrent('USER_NAME', async () => {
|
||||
const [res] = await exe(`
|
||||
<: USER_NAME
|
||||
`);
|
||||
expect(res).toStrictEqual(values.STR('藍'));
|
||||
});
|
||||
|
||||
test.concurrent('USER_USERNAME', async () => {
|
||||
const [res] = await exe(`
|
||||
<: USER_USERNAME
|
||||
`);
|
||||
expect(res).toStrictEqual(values.STR('ai'));
|
||||
});
|
||||
});
|
||||
|
||||
describe.sequential('not logged in', () => {
|
||||
beforeAll(() => {
|
||||
$iMock = null;
|
||||
});
|
||||
|
||||
test.concurrent('USER_ID', async () => {
|
||||
const [res] = await exe(`
|
||||
<: USER_ID
|
||||
`);
|
||||
expect(res).toStrictEqual(values.NULL);
|
||||
});
|
||||
|
||||
test.concurrent('USER_NAME', async () => {
|
||||
const [res] = await exe(`
|
||||
<: USER_NAME
|
||||
`);
|
||||
expect(res).toStrictEqual(values.NULL);
|
||||
});
|
||||
|
||||
test.concurrent('USER_USERNAME', async () => {
|
||||
const [res] = await exe(`
|
||||
<: USER_USERNAME
|
||||
`);
|
||||
expect(res).toStrictEqual(values.NULL);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dialog', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test.sequential('ok', async () => {
|
||||
osMock.alert.mockImplementationOnce(async ({ type, title, text }) => {
|
||||
expect(type).toBe('success');
|
||||
expect(title).toBe('Hello');
|
||||
expect(text).toBe('world');
|
||||
});
|
||||
const [res] = await exe(`
|
||||
<: Mk:dialog('Hello', 'world', 'success')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.NULL);
|
||||
expect(osMock.alert).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('omit type', async () => {
|
||||
osMock.alert.mockImplementationOnce(async ({ type, title, text }) => {
|
||||
expect(type).toBe('info');
|
||||
expect(title).toBe('Hello');
|
||||
expect(text).toBe('world');
|
||||
});
|
||||
const [res] = await exe(`
|
||||
<: Mk:dialog('Hello', 'world')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.NULL);
|
||||
expect(osMock.alert).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('invalid type', async () => {
|
||||
await expect(() => exe(`
|
||||
<: Mk:dialog('Hello', 'world', 'invalid')
|
||||
`)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
|
||||
expect(osMock.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('confirm', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test.sequential('ok', async () => {
|
||||
osMock.confirm.mockImplementationOnce(async ({ type, title, text }) => {
|
||||
expect(type).toBe('success');
|
||||
expect(title).toBe('Hello');
|
||||
expect(text).toBe('world');
|
||||
return { canceled: false };
|
||||
});
|
||||
const [res] = await exe(`
|
||||
<: Mk:confirm('Hello', 'world', 'success')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.TRUE);
|
||||
expect(osMock.confirm).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('omit type', async () => {
|
||||
osMock.confirm
|
||||
.mockImplementationOnce(async ({ type, title, text }) => {
|
||||
expect(type).toBe('question');
|
||||
expect(title).toBe('Hello');
|
||||
expect(text).toBe('world');
|
||||
return { canceled: false };
|
||||
});
|
||||
const [res] = await exe(`
|
||||
<: Mk:confirm('Hello', 'world')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.TRUE);
|
||||
expect(osMock.confirm).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('canceled', async () => {
|
||||
osMock.confirm.mockImplementationOnce(async ({ type, title, text }) => {
|
||||
expect(type).toBe('question');
|
||||
expect(title).toBe('Hello');
|
||||
expect(text).toBe('world');
|
||||
return { canceled: true };
|
||||
});
|
||||
const [res] = await exe(`
|
||||
<: Mk:confirm('Hello', 'world')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.FALSE);
|
||||
expect(osMock.confirm).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('invalid type', async () => {
|
||||
const confirm = osMock.confirm;
|
||||
await expect(() => exe(`
|
||||
<: Mk:confirm('Hello', 'world', 'invalid')
|
||||
`)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('api', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test.sequential('successful', async () => {
|
||||
misskeyApiMock.mockImplementationOnce(
|
||||
async (endpoint, data, token) => {
|
||||
expect(endpoint).toBe('ping');
|
||||
expect(data).toStrictEqual({});
|
||||
expect(token).toBeNull();
|
||||
return { pong: 1735657200000 };
|
||||
}
|
||||
);
|
||||
const [res] = await exe(`
|
||||
<: Mk:api('ping', {})
|
||||
`);
|
||||
expect(res).toStrictEqual(values.OBJ(new Map([
|
||||
['pong', values.NUM(1735657200000)],
|
||||
])));
|
||||
expect(misskeyApiMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('with token', async () => {
|
||||
misskeyApiMock.mockImplementationOnce(
|
||||
async (endpoint, data, token) => {
|
||||
expect(endpoint).toBe('ping');
|
||||
expect(data).toStrictEqual({});
|
||||
expect(token).toStrictEqual('xxxxxxxx');
|
||||
return { pong: 1735657200000 };
|
||||
}
|
||||
);
|
||||
const [res] = await exe(`
|
||||
<: Mk:api('ping', {}, 'xxxxxxxx')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.OBJ(new Map([
|
||||
['pong', values.NUM(1735657200000 )],
|
||||
])));
|
||||
expect(misskeyApiMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('request failed', async () => {
|
||||
misskeyApiMock.mockRejectedValueOnce('Not Found');
|
||||
const [res] = await exe(`
|
||||
<: Mk:api('this/endpoint/should/not/be/found', {})
|
||||
`);
|
||||
expect(res).toStrictEqual(
|
||||
values.ERROR('request_failed', values.STR('Not Found'))
|
||||
);
|
||||
expect(misskeyApiMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test.sequential('invalid endpoint', async () => {
|
||||
await expect(() => exe(`
|
||||
Mk:api('https://example.com/api/ping', {})
|
||||
`)).rejects.toStrictEqual(
|
||||
new errors.AiScriptRuntimeError('invalid endpoint'),
|
||||
);
|
||||
expect(misskeyApiMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test.sequential('missing param', async () => {
|
||||
await expect(() => exe(`
|
||||
Mk:api('ping')
|
||||
`)).rejects.toStrictEqual(
|
||||
new errors.AiScriptRuntimeError('expected param'),
|
||||
);
|
||||
expect(misskeyApiMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('save and load', () => {
|
||||
beforeEach(() => {
|
||||
miLocalStorage.removeItem('aiscript:widget:key');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
miLocalStorage.removeItem('aiscript:widget:key');
|
||||
});
|
||||
|
||||
test.sequential('successful', async () => {
|
||||
const [res] = await exe(`
|
||||
Mk:save('key', 'value')
|
||||
<: Mk:load('key')
|
||||
`);
|
||||
expect(miLocalStorage.getItem('aiscript:widget:key')).toBe('"value"');
|
||||
expect(res).toStrictEqual(values.STR('value'));
|
||||
});
|
||||
|
||||
test.sequential('missing value to save', async () => {
|
||||
await expect(() => exe(`
|
||||
Mk:save('key')
|
||||
`)).rejects.toStrictEqual(
|
||||
new errors.AiScriptRuntimeError('Expect anything, but got nothing.'),
|
||||
);
|
||||
});
|
||||
|
||||
test.sequential('not value found to load', async () => {
|
||||
const [res] = await exe(`
|
||||
<: Mk:load('key')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.NULL);
|
||||
});
|
||||
|
||||
test.sequential('remove existing', async () => {
|
||||
const res = await exe(`
|
||||
Mk:save('key', 'value')
|
||||
<: Mk:load('key')
|
||||
<: Mk:remove('key')
|
||||
<: Mk:load('key')
|
||||
`);
|
||||
expect(res).toStrictEqual([values.STR('value'), values.NULL, values.NULL]);
|
||||
});
|
||||
|
||||
test.sequential('remove nothing', async () => {
|
||||
const res = await exe(`
|
||||
<: Mk:load('key')
|
||||
<: Mk:remove('key')
|
||||
<: Mk:load('key')
|
||||
`);
|
||||
expect(res).toStrictEqual([values.NULL, values.NULL, values.NULL]);
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('url', async () => {
|
||||
vi.stubGlobal('location', { href: 'https://example.com/' });
|
||||
const [res] = await exe(`
|
||||
<: Mk:url()
|
||||
`);
|
||||
expect(res).toStrictEqual(values.STR('https://example.com/'));
|
||||
});
|
||||
|
||||
test.concurrent('nyaize', async () => {
|
||||
const [res] = await exe(`
|
||||
<: Mk:nyaize('な')
|
||||
`);
|
||||
expect(res).toStrictEqual(values.STR('にゃ'));
|
||||
});
|
||||
});
|
23
packages/frontend/test/aiscript/common.test.ts
Normal file
23
packages/frontend/test/aiscript/common.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { assertStringAndIsIn } from "@/scripts/aiscript/common.js";
|
||||
import { values } from "@syuilo/aiscript";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
describe('AiScript common script', () => {
|
||||
test('assertStringAndIsIn', () => {
|
||||
expect(
|
||||
() => assertStringAndIsIn(values.STR('a'), ['a', 'b'])
|
||||
).not.toThrow();
|
||||
expect(
|
||||
() => assertStringAndIsIn(values.STR('c'), ['a', 'b'])
|
||||
).toThrow('"c" is not in "a", "b"');
|
||||
expect(() => assertStringAndIsIn(
|
||||
values.STR('invalid'),
|
||||
['left', 'center', 'right']
|
||||
)).toThrow('"invalid" is not in "left", "center", "right"');
|
||||
});
|
||||
});
|
825
packages/frontend/test/aiscript/ui.test.ts
Normal file
825
packages/frontend/test/aiscript/ui.test.ts
Normal file
@@ -0,0 +1,825 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { registerAsUiLib } from '@/scripts/aiscript/ui.js';
|
||||
import { errors, Interpreter, Parser, values } from '@syuilo/aiscript';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { type Ref, ref } from 'vue';
|
||||
import type {
|
||||
AsUiButton,
|
||||
AsUiButtons,
|
||||
AsUiComponent,
|
||||
AsUiMfm,
|
||||
AsUiNumberInput,
|
||||
AsUiRoot,
|
||||
AsUiSelect,
|
||||
AsUiSwitch,
|
||||
AsUiText,
|
||||
AsUiTextarea,
|
||||
AsUiTextInput,
|
||||
} from '@/scripts/aiscript/ui.js';
|
||||
|
||||
type ExeResult = {
|
||||
root: AsUiRoot;
|
||||
get: (id: string) => AsUiComponent;
|
||||
outputs: values.Value[];
|
||||
}
|
||||
async function exe(script: string): Promise<ExeResult> {
|
||||
const rootRef = ref<AsUiRoot>();
|
||||
const componentRefs = ref<Ref<AsUiComponent>[]>([]);
|
||||
const outputs: values.Value[] = [];
|
||||
|
||||
const interpreter = new Interpreter(
|
||||
registerAsUiLib(componentRefs.value, (root) => {
|
||||
rootRef.value = root.value;
|
||||
}),
|
||||
{
|
||||
out: (value) => {
|
||||
outputs.push(value);
|
||||
}
|
||||
}
|
||||
);
|
||||
const ast = Parser.parse(script);
|
||||
await interpreter.exec(ast);
|
||||
|
||||
const root = rootRef.value;
|
||||
if (root === undefined) {
|
||||
expect.unreachable('root must not be undefined');
|
||||
}
|
||||
const components = componentRefs.value.map(
|
||||
(componentRef) => componentRef.value,
|
||||
);
|
||||
expect(root).toBe(components[0]);
|
||||
expect(root.type).toBe('root');
|
||||
const get = (id: string) => {
|
||||
const component = componentRefs.value.find(
|
||||
(componentRef) => componentRef.value.id === id,
|
||||
);
|
||||
if (component === undefined) {
|
||||
expect.unreachable(`component "${id}" is not defined`);
|
||||
}
|
||||
return component.value;
|
||||
};
|
||||
return { root, get, outputs };
|
||||
}
|
||||
|
||||
describe('AiScript UI API', () => {
|
||||
test.concurrent('root', async () => {
|
||||
const { root } = await exe('');
|
||||
expect(root.children).toStrictEqual([]);
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
test.concurrent('some', async () => {
|
||||
const { outputs } = await exe(`
|
||||
Ui:C:text({}, 'id')
|
||||
<: Ui:get('id')
|
||||
`);
|
||||
const output = outputs[0] as values.VObj;
|
||||
expect(output.type).toBe('obj');
|
||||
expect(output.value.size).toBe(2);
|
||||
expect(output.value.get('id')).toStrictEqual(values.STR('id'));
|
||||
expect(output.value.get('update')!.type).toBe('fn');
|
||||
});
|
||||
|
||||
test.concurrent('none', async () => {
|
||||
const { outputs } = await exe(`
|
||||
<: Ui:get('id')
|
||||
`);
|
||||
expect(outputs).toStrictEqual([values.NULL]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
test.concurrent('normal', async () => {
|
||||
const { get } = await exe(`
|
||||
let text = Ui:C:text({ text: 'a' }, 'id')
|
||||
text.update({ text: 'b' })
|
||||
`);
|
||||
const text = get('id') as AsUiText;
|
||||
expect(text.text).toBe('b');
|
||||
});
|
||||
|
||||
test.concurrent('skip unknown key', async () => {
|
||||
const { get } = await exe(`
|
||||
let text = Ui:C:text({ text: 'a' }, 'id')
|
||||
text.update({
|
||||
text: 'b'
|
||||
unknown: null
|
||||
})
|
||||
`);
|
||||
const text = get('id') as AsUiText;
|
||||
expect(text.text).toBe('b');
|
||||
expect('unknown' in text).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('container', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get } = await exe(`
|
||||
let text = Ui:C:text({
|
||||
text: 'text'
|
||||
}, 'id1')
|
||||
let container = Ui:C:container({
|
||||
children: [text]
|
||||
align: 'left'
|
||||
bgColor: '#fff'
|
||||
fgColor: '#000'
|
||||
font: 'sans-serif'
|
||||
borderWidth: 1
|
||||
borderColor: '#f00'
|
||||
borderStyle: 'hidden'
|
||||
borderRadius: 2
|
||||
padding: 3
|
||||
rounded: true
|
||||
hidden: false
|
||||
}, 'id2')
|
||||
Ui:render([container])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id2']);
|
||||
expect(get('id2')).toStrictEqual({
|
||||
type: 'container',
|
||||
id: 'id2',
|
||||
children: ['id1'],
|
||||
align: 'left',
|
||||
bgColor: '#fff',
|
||||
fgColor: '#000',
|
||||
font: 'sans-serif',
|
||||
borderColor: '#f00',
|
||||
borderWidth: 1,
|
||||
borderStyle: 'hidden',
|
||||
borderRadius: 2,
|
||||
padding: 3,
|
||||
rounded: true,
|
||||
hidden: false,
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:container({}, 'id')
|
||||
`);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'container',
|
||||
id: 'id',
|
||||
children: [],
|
||||
align: undefined,
|
||||
fgColor: undefined,
|
||||
bgColor: undefined,
|
||||
font: undefined,
|
||||
borderWidth: undefined,
|
||||
borderColor: undefined,
|
||||
borderStyle: undefined,
|
||||
borderRadius: undefined,
|
||||
padding: undefined,
|
||||
rounded: undefined,
|
||||
hidden: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('invalid children', async () => {
|
||||
await expect(() => exe(`
|
||||
Ui:C:container({
|
||||
children: 0
|
||||
})
|
||||
`)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
|
||||
});
|
||||
|
||||
test.concurrent('invalid align', async () => {
|
||||
await expect(() => exe(`
|
||||
Ui:C:container({
|
||||
align: 'invalid'
|
||||
})
|
||||
`)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
|
||||
});
|
||||
|
||||
test.concurrent('invalid font', async () => {
|
||||
await expect(() => exe(`
|
||||
Ui:C:container({
|
||||
font: 'invalid'
|
||||
})
|
||||
`)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
|
||||
});
|
||||
|
||||
test.concurrent('invalid borderStyle', async () => {
|
||||
await expect(() => exe(`
|
||||
Ui:C:container({
|
||||
borderStyle: 'invalid'
|
||||
})
|
||||
`)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('text', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get } = await exe(`
|
||||
let text = Ui:C:text({
|
||||
text: 'a'
|
||||
size: 1
|
||||
bold: true
|
||||
color: '#000'
|
||||
font: 'sans-serif'
|
||||
}, 'id')
|
||||
Ui:render([text])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'text',
|
||||
id: 'id',
|
||||
text: 'a',
|
||||
size: 1,
|
||||
bold: true,
|
||||
color: '#000',
|
||||
font: 'sans-serif',
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:text({}, 'id')
|
||||
`);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'text',
|
||||
id: 'id',
|
||||
text: undefined,
|
||||
size: undefined,
|
||||
bold: undefined,
|
||||
color: undefined,
|
||||
font: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('invalid font', async () => {
|
||||
await expect(() => exe(`
|
||||
Ui:C:text({
|
||||
font: 'invalid'
|
||||
})
|
||||
`)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mfm', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get, outputs } = await exe(`
|
||||
let mfm = Ui:C:mfm({
|
||||
text: 'text'
|
||||
size: 1
|
||||
bold: true
|
||||
color: '#000'
|
||||
font: 'sans-serif'
|
||||
onClickEv: print
|
||||
}, 'id')
|
||||
Ui:render([mfm])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
const { onClickEv, ...mfm } = get('id') as AsUiMfm;
|
||||
expect(mfm).toStrictEqual({
|
||||
type: 'mfm',
|
||||
id: 'id',
|
||||
text: 'text',
|
||||
size: 1,
|
||||
bold: true,
|
||||
color: '#000',
|
||||
font: 'sans-serif',
|
||||
});
|
||||
await onClickEv!('a');
|
||||
expect(outputs).toStrictEqual([values.STR('a')]);
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:mfm({}, 'id')
|
||||
`);
|
||||
const { onClickEv, ...mfm } = get('id') as AsUiMfm;
|
||||
expect(onClickEv).toBeTypeOf('function');
|
||||
expect(mfm).toStrictEqual({
|
||||
type: 'mfm',
|
||||
id: 'id',
|
||||
text: undefined,
|
||||
size: undefined,
|
||||
bold: undefined,
|
||||
color: undefined,
|
||||
font: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('invalid font', async () => {
|
||||
await expect(() => exe(`
|
||||
Ui:C:mfm({
|
||||
font: 'invalid'
|
||||
})
|
||||
`)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('textInput', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get, outputs } = await exe(`
|
||||
let text_input = Ui:C:textInput({
|
||||
onInput: print
|
||||
default: 'a'
|
||||
label: 'b'
|
||||
caption: 'c'
|
||||
}, 'id')
|
||||
Ui:render([text_input])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
const { onInput, ...textInput } = get('id') as AsUiTextInput;
|
||||
expect(textInput).toStrictEqual({
|
||||
type: 'textInput',
|
||||
id: 'id',
|
||||
default: 'a',
|
||||
label: 'b',
|
||||
caption: 'c',
|
||||
});
|
||||
await onInput!('d');
|
||||
expect(outputs).toStrictEqual([values.STR('d')]);
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:textInput({}, 'id')
|
||||
`);
|
||||
const { onInput, ...textInput } = get('id') as AsUiTextInput;
|
||||
expect(onInput).toBeTypeOf('function');
|
||||
expect(textInput).toStrictEqual({
|
||||
type: 'textInput',
|
||||
id: 'id',
|
||||
default: undefined,
|
||||
label: undefined,
|
||||
caption: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('textarea', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get, outputs } = await exe(`
|
||||
let textarea = Ui:C:textarea({
|
||||
onInput: print
|
||||
default: 'a'
|
||||
label: 'b'
|
||||
caption: 'c'
|
||||
}, 'id')
|
||||
Ui:render([textarea])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
const { onInput, ...textarea } = get('id') as AsUiTextarea;
|
||||
expect(textarea).toStrictEqual({
|
||||
type: 'textarea',
|
||||
id: 'id',
|
||||
default: 'a',
|
||||
label: 'b',
|
||||
caption: 'c',
|
||||
});
|
||||
await onInput!('d');
|
||||
expect(outputs).toStrictEqual([values.STR('d')]);
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:textarea({}, 'id')
|
||||
`);
|
||||
const { onInput, ...textarea } = get('id') as AsUiTextarea;
|
||||
expect(onInput).toBeTypeOf('function');
|
||||
expect(textarea).toStrictEqual({
|
||||
type: 'textarea',
|
||||
id: 'id',
|
||||
default: undefined,
|
||||
label: undefined,
|
||||
caption: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('numberInput', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get, outputs } = await exe(`
|
||||
let number_input = Ui:C:numberInput({
|
||||
onInput: print
|
||||
default: 1
|
||||
label: 'a'
|
||||
caption: 'b'
|
||||
}, 'id')
|
||||
Ui:render([number_input])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
const { onInput, ...numberInput } = get('id') as AsUiNumberInput;
|
||||
expect(numberInput).toStrictEqual({
|
||||
type: 'numberInput',
|
||||
id: 'id',
|
||||
default: 1,
|
||||
label: 'a',
|
||||
caption: 'b',
|
||||
});
|
||||
await onInput!(2);
|
||||
expect(outputs).toStrictEqual([values.NUM(2)]);
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:numberInput({}, 'id')
|
||||
`);
|
||||
const { onInput, ...numberInput } = get('id') as AsUiNumberInput;
|
||||
expect(onInput).toBeTypeOf('function');
|
||||
expect(numberInput).toStrictEqual({
|
||||
type: 'numberInput',
|
||||
id: 'id',
|
||||
default: undefined,
|
||||
label: undefined,
|
||||
caption: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('button', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get, outputs } = await exe(`
|
||||
let button = Ui:C:button({
|
||||
text: 'a'
|
||||
onClick: @() { <: 'clicked' }
|
||||
primary: true
|
||||
rounded: false
|
||||
disabled: false
|
||||
}, 'id')
|
||||
Ui:render([button])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
const { onClick, ...button } = get('id') as AsUiButton;
|
||||
expect(button).toStrictEqual({
|
||||
type: 'button',
|
||||
id: 'id',
|
||||
text: 'a',
|
||||
primary: true,
|
||||
rounded: false,
|
||||
disabled: false,
|
||||
});
|
||||
await onClick!();
|
||||
expect(outputs).toStrictEqual([values.STR('clicked')]);
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:button({}, 'id')
|
||||
`);
|
||||
const { onClick, ...button } = get('id') as AsUiButton;
|
||||
expect(onClick).toBeTypeOf('function');
|
||||
expect(button).toStrictEqual({
|
||||
type: 'button',
|
||||
id: 'id',
|
||||
text: undefined,
|
||||
primary: undefined,
|
||||
rounded: undefined,
|
||||
disabled: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buttons', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get } = await exe(`
|
||||
let buttons = Ui:C:buttons({
|
||||
buttons: []
|
||||
}, 'id')
|
||||
Ui:render([buttons])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'buttons',
|
||||
id: 'id',
|
||||
buttons: [],
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:buttons({}, 'id')
|
||||
`);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'buttons',
|
||||
id: 'id',
|
||||
buttons: [],
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('some buttons', async () => {
|
||||
const { root, get, outputs } = await exe(`
|
||||
let buttons = Ui:C:buttons({
|
||||
buttons: [
|
||||
{
|
||||
text: 'a'
|
||||
onClick: @() { <: 'clicked a' }
|
||||
primary: true
|
||||
rounded: false
|
||||
disabled: false
|
||||
}
|
||||
{
|
||||
text: 'b'
|
||||
onClick: @() { <: 'clicked b' }
|
||||
primary: true
|
||||
rounded: false
|
||||
disabled: false
|
||||
}
|
||||
]
|
||||
}, 'id')
|
||||
Ui:render([buttons])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
const { buttons, ...buttonsOptions } = get('id') as AsUiButtons;
|
||||
expect(buttonsOptions).toStrictEqual({
|
||||
type: 'buttons',
|
||||
id: 'id',
|
||||
});
|
||||
expect(buttons!.length).toBe(2);
|
||||
const { onClick: onClickA, ...buttonA } = buttons![0];
|
||||
expect(buttonA).toStrictEqual({
|
||||
text: 'a',
|
||||
primary: true,
|
||||
rounded: false,
|
||||
disabled: false,
|
||||
});
|
||||
const { onClick: onClickB, ...buttonB } = buttons![1];
|
||||
expect(buttonB).toStrictEqual({
|
||||
text: 'b',
|
||||
primary: true,
|
||||
rounded: false,
|
||||
disabled: false,
|
||||
});
|
||||
await onClickA!();
|
||||
await onClickB!();
|
||||
expect(outputs).toStrictEqual(
|
||||
[values.STR('clicked a'), values.STR('clicked b')]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get, outputs } = await exe(`
|
||||
let switch = Ui:C:switch({
|
||||
onChange: print
|
||||
default: false
|
||||
label: 'a'
|
||||
caption: 'b'
|
||||
}, 'id')
|
||||
Ui:render([switch])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
const { onChange, ...switchOptions } = get('id') as AsUiSwitch;
|
||||
expect(switchOptions).toStrictEqual({
|
||||
type: 'switch',
|
||||
id: 'id',
|
||||
default: false,
|
||||
label: 'a',
|
||||
caption: 'b',
|
||||
});
|
||||
await onChange!(true);
|
||||
expect(outputs).toStrictEqual([values.TRUE]);
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:switch({}, 'id')
|
||||
`);
|
||||
const { onChange, ...switchOptions } = get('id') as AsUiSwitch;
|
||||
expect(onChange).toBeTypeOf('function');
|
||||
expect(switchOptions).toStrictEqual({
|
||||
type: 'switch',
|
||||
id: 'id',
|
||||
default: undefined,
|
||||
label: undefined,
|
||||
caption: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('select', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get, outputs } = await exe(`
|
||||
let select = Ui:C:select({
|
||||
items: [
|
||||
{ text: 'A', value: 'a' }
|
||||
{ text: 'B', value: 'b' }
|
||||
]
|
||||
onChange: print
|
||||
default: 'a'
|
||||
label: 'c'
|
||||
caption: 'd'
|
||||
}, 'id')
|
||||
Ui:render([select])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
const { onChange, ...select } = get('id') as AsUiSelect;
|
||||
expect(select).toStrictEqual({
|
||||
type: 'select',
|
||||
id: 'id',
|
||||
items: [
|
||||
{ text: 'A', value: 'a' },
|
||||
{ text: 'B', value: 'b' },
|
||||
],
|
||||
default: 'a',
|
||||
label: 'c',
|
||||
caption: 'd',
|
||||
});
|
||||
await onChange!('b');
|
||||
expect(outputs).toStrictEqual([values.STR('b')]);
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:select({}, 'id')
|
||||
`);
|
||||
const { onChange, ...select } = get('id') as AsUiSelect;
|
||||
expect(onChange).toBeTypeOf('function');
|
||||
expect(select).toStrictEqual({
|
||||
type: 'select',
|
||||
id: 'id',
|
||||
items: [],
|
||||
default: undefined,
|
||||
label: undefined,
|
||||
caption: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('omit item values', async () => {
|
||||
const { get } = await exe(`
|
||||
let select = Ui:C:select({
|
||||
items: [
|
||||
{ text: 'A' }
|
||||
{ text: 'B' }
|
||||
]
|
||||
}, 'id')
|
||||
`);
|
||||
const { onChange, ...select } = get('id') as AsUiSelect;
|
||||
expect(onChange).toBeTypeOf('function');
|
||||
expect(select).toStrictEqual({
|
||||
type: 'select',
|
||||
id: 'id',
|
||||
items: [
|
||||
{ text: 'A', value: 'A' },
|
||||
{ text: 'B', value: 'B' },
|
||||
],
|
||||
default: undefined,
|
||||
label: undefined,
|
||||
caption: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('folder', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get } = await exe(`
|
||||
let folder = Ui:C:folder({
|
||||
children: []
|
||||
title: 'a'
|
||||
opened: true
|
||||
}, 'id')
|
||||
Ui:render([folder])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'folder',
|
||||
id: 'id',
|
||||
children: [],
|
||||
title: 'a',
|
||||
opened: true,
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:folder({}, 'id')
|
||||
`);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'folder',
|
||||
id: 'id',
|
||||
children: [],
|
||||
title: '',
|
||||
opened: true,
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('some children', async () => {
|
||||
const { get } = await exe(`
|
||||
let text = Ui:C:text({
|
||||
text: 'text'
|
||||
}, 'id1')
|
||||
Ui:C:folder({
|
||||
children: [text]
|
||||
}, 'id2')
|
||||
`);
|
||||
expect(get('id2')).toStrictEqual({
|
||||
type: 'folder',
|
||||
id: 'id2',
|
||||
children: ['id1'],
|
||||
title: '',
|
||||
opened: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('postFormButton', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get } = await exe(`
|
||||
let post_form_button = Ui:C:postFormButton({
|
||||
text: 'a'
|
||||
primary: true
|
||||
rounded: false
|
||||
form: {
|
||||
text: 'b'
|
||||
cw: 'c'
|
||||
visibility: 'public'
|
||||
localOnly: true
|
||||
}
|
||||
}, 'id')
|
||||
Ui:render([post_form_button])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'postFormButton',
|
||||
id: 'id',
|
||||
text: 'a',
|
||||
primary: true,
|
||||
rounded: false,
|
||||
form: {
|
||||
text: 'b',
|
||||
cw: 'c',
|
||||
visibility: 'public',
|
||||
localOnly: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:postFormButton({}, 'id')
|
||||
`);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'postFormButton',
|
||||
id: 'id',
|
||||
text: undefined,
|
||||
primary: undefined,
|
||||
rounded: undefined,
|
||||
form: { text: '' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('postForm', () => {
|
||||
test.concurrent('all options', async () => {
|
||||
const { root, get } = await exe(`
|
||||
let post_form = Ui:C:postForm({
|
||||
form: {
|
||||
text: 'a'
|
||||
cw: 'b'
|
||||
visibility: 'public'
|
||||
localOnly: true
|
||||
}
|
||||
}, 'id')
|
||||
Ui:render([post_form])
|
||||
`);
|
||||
expect(root.children).toStrictEqual(['id']);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'postForm',
|
||||
id: 'id',
|
||||
form: {
|
||||
text: 'a',
|
||||
cw: 'b',
|
||||
visibility: 'public',
|
||||
localOnly: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('minimum options', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:postForm({}, 'id')
|
||||
`);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'postForm',
|
||||
id: 'id',
|
||||
form: { text: '' },
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('minimum options for form', async () => {
|
||||
const { get } = await exe(`
|
||||
Ui:C:postForm({
|
||||
form: { text: '' }
|
||||
}, 'id')
|
||||
`);
|
||||
expect(get('id')).toStrictEqual({
|
||||
type: 'postForm',
|
||||
id: 'id',
|
||||
form: {
|
||||
text: '',
|
||||
cw: undefined,
|
||||
visibility: undefined,
|
||||
localOnly: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user