/* * Tencent is pleased to support the open source community by making TMagicEditor available. * * Copyright (C) 2025 Tencent. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { assert, describe, expect, test } from 'vitest'; import type { DataSchema } from '@tmagic/schema'; import * as util from '../../src'; describe('asyncLoadJs', () => { const url = 'https://m.www.tmagic.com/magic-ui/production/1/1625056093304/magic/magic-ui.umd.min.js'; // 在测试环境中,浏览器不会真的加载外部 script。 // 通过监听插入到 head 的 script 节点,手动派发 load 事件来驱动 Promise resolve。 const triggerScriptLoad = () => { queueMicrotask(() => { const scripts = document.getElementsByTagName('script'); Array.from(scripts).forEach((s) => { if (typeof s.onload === 'function') { (s.onload as any)(new Event('load')); } }); }); }; test('第一次加载asyncLoadJs带url与crossorigin参数', async () => { const crossOrigin = 'anonymous'; const load = util.asyncLoadJs(url, crossOrigin); triggerScriptLoad(); await load; const script = document.getElementsByTagName('script')[0]; expect(script).not.toBeUndefined(); expect(script.type).toMatch('text/javascript'); expect(script.crossOrigin).toMatch(crossOrigin); expect(script.src).toMatch(url); }); test('第二次加载asyncLoadJs', async () => { const load1 = util.asyncLoadJs(url, 'anonymous'); triggerScriptLoad(); await load1; await util.asyncLoadJs(url, 'use-credentials'); const scriptList = document.getElementsByTagName('script'); expect(scriptList.length).toBe(1); expect(scriptList[0].crossOrigin).toMatch('anonymous'); expect(scriptList[0].src).toMatch(url); }); test('url无效, onerror 触发后 reject', async () => { const promise = util.asyncLoadJs('error-url'); queueMicrotask(() => { const scripts = document.getElementsByTagName('script'); Array.from(scripts).forEach((s) => { if (s.src.includes('error-url') && typeof s.onerror === 'function') { (s.onerror as any)(new Event('error')); } }); }); await expect(promise).rejects.toBeInstanceOf(Error); }); }); describe('asyncLoadCss', () => { const url = 'https://beta.m.www.tmagic.com/magic-act/css/BuyGift.75d837d2b3fd.css?max_age=864000'; const triggerLinkLoad = () => { queueMicrotask(() => { const links = document.getElementsByTagName('link'); Array.from(links).forEach((l) => { if (typeof l.onload === 'function') { (l.onload as any)(new Event('load')); } }); }); }; test('第一次加载asyncLoadCss', async () => { const load = util.asyncLoadCss(url); triggerLinkLoad(); await load; const link = document.getElementsByTagName('link')[0]; expect(link).not.toBeUndefined(); expect(link.rel).toMatch('stylesheet'); expect(link.href).toMatch(url); }); test('第二次加载asyncLoadCss命中缓存', async () => { const load1 = util.asyncLoadCss(url); triggerLinkLoad(); await load1; await util.asyncLoadCss(url); const linkList = document.getElementsByTagName('link'); expect(linkList.length).toBe(1); expect(linkList[0].href).toMatch(url); }); test('url无效, onerror 触发后 reject', async () => { const promise = util.asyncLoadCss('error-css'); queueMicrotask(() => { const links = document.getElementsByTagName('link'); Array.from(links).forEach((l) => { if (l.href.includes('error-css') && typeof l.onerror === 'function') { (l.onerror as any)(new Event('error')); } }); }); await expect(promise).rejects.toBeInstanceOf(Error); }); }); describe('toLine', () => { test('aBc', () => { const value = util.toLine('aBc'); expect(value).toBe('a-bc'); }); test('aBC', () => { const value = util.toLine('aBC'); expect(value).toBe('a-b-c'); }); test('ABC', () => { const value = util.toLine('ABC'); expect(value).toBe('a-b-c'); }); }); describe('toHump', () => { test('a-bc', () => { const value = util.toHump('a-bc'); expect(value).toBe('aBc'); }); test('a-b-c', () => { const value = util.toHump('a-b-c'); expect(value).toBe('aBC'); }); test('-b-c', () => { const value = util.toHump('-b-c'); expect(value).toBe('BC'); }); }); describe('getNodePath', () => { const root = [ { id: 1, type: 'container', items: [ { id: 11, items: [ { id: 111, }, ], }, ], }, { id: 2, type: 'container', items: [ { id: 22, items: [ { id: 222, }, ], }, ], }, ]; test('基础', () => { const path = util.getNodePath(111, root); const path2 = util.getNodePath(22, root); expect(path).toHaveLength(3); expect(path2).toHaveLength(2); }); test('error', () => { const path = util.getNodePath(111, 123 as any); const path2 = util.getNodePath(33, root); expect(path).toHaveLength(0); expect(path2).toHaveLength(0); }); }); describe('filterXSS', () => { test('<>', () => { const value = util.filterXSS('
'); expect(value).toBe('<div></div>'); }); test('\'"', () => { const value = util.filterXSS('\'div\'"span"'); expect(value).toBe(''div'"span"'); }); }); describe('getUrlParam', () => { test('正常', () => { const url = 'http://www.tmagic.com?a=b'; const value = util.getUrlParam('a', url); expect(value).toBe('b'); }); test('null', () => { const url = 'http://www.tmagic.com'; const value = util.getUrlParam('a', url); expect(value).toBe(''); }); test('emprty', () => { const url = 'http://www.tmagic.com?a='; const value = util.getUrlParam('a', url); expect(value).toBe(''); }); }); describe('setUrlParam', () => { test('正常', () => { expect(util.setUrlParam('a', '1', 'https://www.tmagic.com')).toBe('https://www.tmagic.com?a=1'); expect(util.setUrlParam('a', '1', 'https://www.tmagic.com?c&d')).toBe('https://www.tmagic.com?c&d&a=1'); expect(util.setUrlParam('a', '1', 'https://www.tmagic.com?b=1')).toBe('https://www.tmagic.com?b=1&a=1'); }); }); describe('getSearchObj', () => { test('正常', () => { expect(util.getSearchObj('a=1&b=2')).toEqual({ a: '1', b: '2' }); }); }); describe('delQueStr', () => { test('正常', () => { expect(util.delQueStr('https://www.tmagic.com?a=1', 'a')).toBe('https://www.tmagic.com'); expect(util.delQueStr('https://www.tmagic.com?a=1&b=2', ['a', 'b'])).toBe('https://www.tmagic.com'); expect(util.delQueStr('https://www.tmagic.com?a=1&b=2', ['a'])).toBe('https://www.tmagic.com?b=2'); }); }); describe('isPop', () => { // type 为 pop 结尾 isPop 才为 true test('true', () => { expect( util.isPop({ type: 'pop', id: 1, }), ).toBeTruthy(); }); test('endswidth true', () => { expect( util.isPop({ type: 'xxxpop', id: 1, }), ).toBeTruthy(); }); test('false', () => { expect( util.isPop({ type: 'pop1', id: 1, }), ).toBeFalsy(); }); }); describe('isPage', () => { test('true', () => { expect( util.isPage({ type: 'page', id: 1, }), ).toBeTruthy(); }); test('false', () => { expect( util.isPage({ type: 'pop1', id: 1, }), ).toBeFalsy(); }); }); describe('getHost', () => { test('正常', () => { const host = util.getHost('https://www.tmagic.com/index.html'); expect(host).toBe('www.tmagic.com'); }); }); describe('isSameDomain', () => { test('正常', () => { const flag = util.isSameDomain('https://www.tmagic.com/index.html', 'www.tmagic.com'); expect(flag).toBeTruthy(); }); test('不正常', () => { const flag = util.isSameDomain('https://www.tmagic.com/index.html', 'test.www.tmagic.com'); expect(flag).toBeFalsy(); }); test('不是http', () => { const flag = util.isSameDomain('ftp://www.tmagic.com/index.html', 'test.www.tmagic.com'); expect(flag).toBeTruthy(); }); }); describe('guid', () => { test('获取id', () => { const id = util.guid(); const id1 = util.guid(); expect(typeof id).toBe('string'); expect(id === id1).toBeFalsy(); }); }); describe('getValueByKeyPath', () => { test('key', () => { const value = util.getValueByKeyPath('a', { a: 1, }); expect(value).toBe(1); }); test('keys', () => { const value = util.getValueByKeyPath('a.b', { a: { b: 1, }, }); expect(value).toBe(1); }); test('array', () => { const value = util.getValueByKeyPath('a.0.b', { a: [ { b: 1, }, ], }); expect(value).toBe(1); const value1 = util.getValueByKeyPath('a[0].b', { a: [ { b: 1, }, ], }); expect(value1).toBe(1); }); test('error', () => { assert.throws(() => { util.getValueByKeyPath('a.b.c.d', { a: {}, }); }); assert.throws(() => { util.getValueByKeyPath('a.b.c', { a: {}, }); }); assert.doesNotThrow(() => { util.getValueByKeyPath('a', { a: {}, }); }); }); }); describe('getNodes', () => { test('获取id', () => { const root = [ { id: 1, type: 'container', items: [ { id: 11, items: [ { id: 111, }, ], }, ], }, { id: 2, type: 'container', items: [ { id: 22, items: [ { id: 222, }, ], }, ], }, { id: 3, type: 'container', items: { id: 33, items: [ { id: 333, }, ], }, }, ]; const nodes = util.getNodes([22, 111, 2], root); expect(nodes.length).toBe(3); }); }); describe('getDepKeys', () => { test('get keys', () => { const keys = util.getDepKeys( { ds_bebcb2d5: { 61705611: { name: '文本', keys: ['text'], }, }, }, 61705611, ); expect(keys).toEqual(['text']); }); }); describe('getDepNodeIds', () => { test('get node ids', () => { const ids = util.getDepNodeIds({ ds_bebcb2d5: { 61705611: { name: '文本', keys: ['text'], }, }, }); expect(ids).toEqual(['61705611']); }); }); describe('replaceChildNode', () => { test('replace', () => { const root = [ { id: 1, text: '', type: 'container', items: [ { id: 11, text: '', items: [ { id: 111, text: '', }, ], }, ], }, { id: 2, type: 'container', text: '', items: [ { id: 22, text: '', items: [ { id: 222, text: '', }, ], }, ], }, ]; expect(root[1].items[0].items[0].text).toBe(''); util.replaceChildNode( { id: 222, text: '文本', }, root, ); expect(root[1].items[0].items[0].text).toBe('文本'); }); test('replace with parent', () => { const root = [ { id: 1, text: '', type: 'container', items: [ { id: 11, text: '', items: [ { id: 111, text: '', }, ], }, ], }, { id: 2, type: 'container', text: '', items: [ { id: 22, text: '', items: [ { id: 222, text: '', }, ], }, ], }, ]; expect(root[1].items[0].items[0].text).toBe(''); util.replaceChildNode( { id: 222, text: '文本', }, root, 22, ); expect(root[1].items[0].items[0].text).toBe('文本'); }); }); describe('compiledNode', () => { test('compiled', () => { const node = util.compiledNode( (_str: string) => '123', { id: 61705611, type: 'text', text: { value: '456', }, }, { ds_bebcb2d5: { 61705611: { name: '文本', keys: ['text.value'], }, }, }, ); expect(node.text.value).toBe('123'); }); test('compile with source id', () => { const node = util.compiledNode( (_str: string) => '123', { id: 61705611, type: 'text', text: '456', }, { ds_bebcb2d5: { 61705611: { name: '文本', keys: ['text'], }, }, }, 'ds_bebcb2d5', ); expect(node.text).toBe('123'); }); test('compile error', () => { const node = util.compiledNode( (_str: string) => { throw new Error('error'); }, { id: 61705611, type: 'text', text: '456', }, { ds_bebcb2d5: { 61705611: { name: '文本', keys: ['text'], }, }, }, ); expect(node.text).toBe(''); }); test('compile 缓存命中时直接复用 cache value', () => { const node: any = { id: 61705611, type: 'text', [`${util.DSL_NODE_KEY_COPY_PREFIX}text`]: 'cached-template', text: 'old-value', }; const result = util.compiledNode((str: string) => `compiled-${str}`, node, { ds_bebcb2d5: { 61705611: { name: '文本', keys: ['text'], }, }, }); expect(result.text).toBe('compiled-cached-template'); }); }); describe('getDefaultValueFromFields', () => { test('最简单', () => { const fields = [ { name: 'name', }, ]; const data = util.getDefaultValueFromFields(fields); expect(data).toHaveProperty('name'); }); test('默认值为string', () => { const fields = [ { name: 'name', defaultValue: 'name', }, ]; const data = util.getDefaultValueFromFields(fields); expect(data.name).toBe('name'); }); test('type 为 object', () => { const fields: DataSchema[] = [ { type: 'object', name: 'name', }, ]; const data = util.getDefaultValueFromFields(fields); expect(data.name).toEqual({}); }); test('type 为 array', () => { const fields: DataSchema[] = [ { type: 'array', name: 'name', }, ]; const data = util.getDefaultValueFromFields(fields); expect(data.name).toEqual([]); }); test('type 为 null', () => { const fields: DataSchema[] = [ { type: 'null', name: 'name', }, ]; const data = util.getDefaultValueFromFields(fields); expect(data.name).toBeNull(); }); test('object 嵌套', () => { const fields: DataSchema[] = [ { type: 'object', name: 'name', fields: [ { name: 'key', defaultValue: 'key', }, ], }, ]; const data = util.getDefaultValueFromFields(fields); expect(data.name.key).toBe('key'); }); }); describe('compiledCond', () => { test('is', () => { expect(util.compiledCond('is', undefined, 1)).toBeFalsy(); expect(util.compiledCond('is', 1, 1)).toBeTruthy(); expect(util.compiledCond('is', '1', 1)).toBeFalsy(); expect(util.compiledCond('is', NaN, 1)).toBeFalsy(); expect(util.compiledCond('is', NaN, undefined)).toBeFalsy(); }); }); describe('getKeysArray', () => { // 测试普通数组索引转换 test('应该将array[0]转换为["array", "0"]', () => { const input = 'array[0]'; const expected = ['array', '0']; const result = util.getKeysArray(input); expect(result).toEqual(expected); }); // 测试多层数组索引转换 test('应该将array[0][1]转换为["array", "0", "1"]', () => { const input = 'array[0][1]'; const expected = ['array', '0', '1']; const result = util.getKeysArray(input); expect(result).toEqual(expected); }); // 测试数字输入 test('应该将数字123转换为["123"]', () => { const input = 123; const expected = ['123']; const result = util.getKeysArray(input); expect(result).toEqual(expected); }); // 测试不含数组索引的字符串 test('应该将普通字符串按点号分割', () => { const input = 'a.b.c'; const expected = ['a', 'b', 'c']; const result = util.getKeysArray(input); expect(result).toEqual(expected); }); // 测试空字符串 test('应该正确处理空字符串', () => { const input = ''; const expected = ['']; const result = util.getKeysArray(input); expect(result).toEqual(expected); }); // 测试混合格式 test('应该正确处理混合格式的字符串', () => { const input = 'obj.array[0].prop[1]'; const expected = ['obj', 'array', '0', 'prop', '1']; const result = util.getKeysArray(input); expect(result).toEqual(expected); }); // 测试特殊字符情况 test('应该正确处理包含特殊字符的字符串', () => { const input = 'obj.array[0].prop-with-dash[1]'; const expected = ['obj', 'array', '0', 'prop-with-dash', '1']; const result = util.getKeysArray(input); expect(result).toEqual(expected); }); // 测试只有数组索引的情况 test('应该正确处理只有数组索引的情况', () => { const input = '[0][1]'; const expected = ['', '0', '1']; const result = util.getKeysArray(input); expect(result).toEqual(expected); }); });