| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- import test from 'node:test';
- import assert from 'node:assert/strict';
- import { readFile } from 'node:fs/promises';
- import { createRequire } from 'node:module';
- import { JSDOM } from 'jsdom';
- import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc';
- import ts from 'typescript';
- // Mount the actual uni-app checkbox, group and label; do not replace their click behavior.
- const dom = new JSDOM('<!doctype html><html><body></body></html>', {
- url: 'http://localhost/',
- });
- for (const key of ['window', 'document', 'Element', 'HTMLElement', 'SVGElement', 'Node']) {
- globalThis[key] = dom.window[key];
- }
- globalThis.__uniConfig = {};
- globalThis.getCurrentPages = () => [];
- const require = createRequire(import.meta.url);
- const vue = require('vue');
- const { Checkbox, CheckboxGroup, Label, Input } = require('@dcloudio/uni-h5');
- const source = await readFile(new URL('../src/pages/account/login.vue', import.meta.url), 'utf8');
- const { descriptor } = parse(source, { filename: 'login.vue' });
- const script = compileScript(descriptor, { id: 'login-agreement-dom' });
- const components = {
- checkbox: 'UniCheckbox',
- 'checkbox-group': 'UniCheckboxGroup',
- label: 'UniLabel',
- input: 'UniInput',
- };
- const template = compileTemplate({
- source: descriptor.template.content.replace(
- /<(\/?)(checkbox-group|checkbox|label|input)(?=[\s/>])/g,
- (_, closing, tag) => '<' + closing + components[tag],
- ),
- filename: 'login.vue',
- id: 'login-agreement-dom',
- compilerOptions: {
- bindingMetadata: script.bindings,
- isNativeTag: (tag) => ['view', 'text', 'button'].includes(tag),
- },
- });
- assert.deepEqual(template.errors, []);
- function evaluate(code, dependencies) {
- const { outputText } = ts.transpileModule(code, {
- compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS },
- });
- const exports = {};
- new Function('require', 'exports', outputText)((name) => {
- if (name in dependencies) return dependencies[name];
- throw new Error('Unexpected dependency: ' + name);
- }, exports);
- return exports;
- }
- async function click(element) {
- assert.ok(element, 'click target exists');
- element.click();
- await vue.nextTick();
- await Promise.resolve();
- }
- async function fixture(t, code = '', phone = '13012345678') {
- const posts = [],
- messages = [],
- accepted = [],
- navigations = [];
- globalThis.uni = { reLaunch: ({ url }) => navigations.push(url) };
- const configuration = {
- privacy_url: 'https://example.test/privacy',
- capabilities: { sms_login: true, test_phone_login: true },
- };
- const login = { access_token: 'fixture-token', user_id: '42', identities: [] };
- const component = evaluate(script.content, {
- vue,
- '@dcloudio/uni-app': { onShow() {} },
- '../../stores/session': {
- session: vue.reactive({ configuration }),
- loadConfiguration() {},
- acceptLogin: (value) => accepted.push(value),
- accountPage: () => '/pages/home/index',
- },
- '../../api/client': {},
- '../../utils/format': {
- toast: (message) => messages.push(message),
- go: (url) => navigations.push(url),
- },
- '../../utils/login-flow': {
- isTestPhone: (phone) => ['13012345678', '13012345679', '13012345680'].includes(phone),
- requestPhoneCode: async () => true,
- loginByPhoneCode: async (phone, code) => {
- posts.push({ phone, code });
- return login;
- },
- },
- }).default;
- component.render = evaluate(template.code, { vue }).render;
- const root = document.createElement('div');
- document.body.append(root);
- const app = vue.createApp(component);
- app.component('UniCheckbox', Checkbox);
- app.component('UniCheckboxGroup', CheckboxGroup);
- app.component('UniLabel', Label);
- app.component('UniInput', Input);
- app.mount(root);
- t.after(() => {
- app.unmount();
- root.remove();
- });
- await click(root.querySelector('.login-option'));
- const phoneInput = root.querySelector('.phone-login input');
- phoneInput.value = phone;
- phoneInput.dispatchEvent(new window.Event('input', { bubbles: true }));
- await vue.nextTick();
- const codeInput = root.querySelector('.code-field input');
- if (codeInput) {
- codeInput.value = code;
- codeInput.dispatchEvent(new window.Event('input', { bubbles: true }));
- }
- await vue.nextTick();
- const consent = () =>
- [...root.querySelectorAll('uni-label text')].find((element) =>
- element.textContent.includes('我已阅读并同意'),
- );
- const checked = () => Boolean(root.querySelector('uni-checkbox svg'));
- const submit = () => click(root.querySelector('.phone-login .primary'));
- return { root, posts, messages, accepted, navigations, consent, checked, submit };
- }
- test('clicking the agreement text updates the actual checkbox and permits login', async (t) => {
- const app = await fixture(t);
- await app.submit();
- assert.match(app.messages.at(-1), /请先阅读并同意/);
- await click(app.consent());
- assert.equal(app.checked(), true);
- await app.submit();
- assert.equal(app.root.querySelector('.code-field'), null);
- assert.deepEqual(app.posts, [{ phone: '13012345678', code: '' }]);
- assert.equal(app.accepted.length, 1);
- });
- test('checkbox and label clicks remain synchronized when repeatedly selecting and clearing', async (t) => {
- const app = await fixture(t);
- await click(app.root.querySelector('uni-checkbox'));
- assert.equal(app.checked(), true);
- await click(app.consent());
- assert.equal(app.checked(), false);
- await app.submit();
- assert.equal(app.posts.length, 0);
- assert.match(app.messages.at(-1), /请先阅读并同意/);
- await click(app.consent());
- await app.submit();
- assert.equal(app.posts.length, 1);
- });
- test('opening the privacy link never changes agreement selection', async (t) => {
- const app = await fixture(t);
- await click(app.root.querySelector('.check-row .link'));
- assert.equal(app.checked(), false);
- await app.submit();
- assert.equal(app.posts.length, 0);
- await click(app.consent());
- await click(app.root.querySelector('.check-row .link'));
- assert.equal(app.checked(), true);
- await app.submit();
- assert.equal(app.posts.length, 1);
- });
- test('an agreed real phone login still rejects an invalid SMS code', async (t) => {
- const app = await fixture(t, '66666', '13800138000');
- await click(app.consent());
- await app.submit();
- assert.equal(app.posts.length, 0);
- assert.equal(app.messages.at(-1), '请输入6位短信验证码');
- });
|