| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- import test from 'node:test';
- import assert from 'node:assert/strict';
- import { readFile } from 'node:fs/promises';
- import { parse, compileScript } from '@vue/compiler-sfc';
- import ts from 'typescript';
- import * as vue from 'vue';
- const cartSource = await readFile(new URL('../src/stores/cart.ts', import.meta.url), 'utf8');
- const formatSource = await readFile(new URL('../src/utils/format.ts', import.meta.url), 'utf8');
- const componentSource = await readFile(
- new URL('../src/pages/packages/cart.vue', import.meta.url),
- 'utf8',
- );
- const { descriptor } = parse(componentSource, { filename: 'cart.vue' });
- const script = compileScript(descriptor, { id: 'checkout-test' });
- function evaluate(source, dependencies) {
- const { outputText } = ts.transpileModule(source, {
- compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS },
- });
- const exports = {};
- new Function('require', 'exports', outputText)((name) => {
- if (name in dependencies) return dependencies[name];
- if (name.endsWith('.vue')) return {};
- throw new Error('Unexpected dependency: ' + name);
- }, exports);
- return exports;
- }
- globalThis.uni = { showToast() {}, navigateTo() {}, redirectTo() {}, navigateBack() {} };
- const product = () => ({
- id: 1,
- name: '旧套餐',
- sku_id: 1,
- sku_name: '旧规格',
- liters: 18.9,
- buckets: 30,
- price_cents: 69900,
- unit_price_cents: 2330,
- category: null,
- valid_months: null,
- includes_device: null,
- });
- const quote = (name = '新套餐') => ({
- items: [
- {
- package_id: 1,
- name,
- sku_id: 2,
- quantity: 2,
- buckets: 120,
- unit_price_cents: 69950,
- amount_cents: 139900,
- },
- ],
- amount_cents: 139900,
- discount_cents: 0,
- quote_revision: 'a'.repeat(64),
- });
- function deferred() {
- let resolve, reject;
- const promise = new Promise((yes, no) => {
- resolve = yes;
- reject = no;
- });
- return { promise, resolve, reject };
- }
- function fixture() {
- const requests = [];
- const commands = [];
- const session = vue.reactive({ token: 'token', epoch: 0 });
- const format = evaluate(formatSource, {});
- const store = evaluate(cartSource, { vue, '../utils/format': format });
- store.cart.items.push({ product: product(), quantity: 2 });
- const state = evaluate(script.content, {
- vue,
- '@dcloudio/uni-app': { onShow() {} },
- '../../api/client': {
- get: async () => ({}),
- post: () => {
- const pending = deferred();
- requests.push(pending);
- return pending.promise;
- },
- },
- '../../stores/session': {
- session,
- canManageEnterprise: () => true,
- resourcePath: () => '/enterprises/1',
- },
- '../../stores/cart': store,
- '../../utils/format': format,
- '../../composables/useCommand': {
- useCommand: () => ({ busy: false, execute: (payload) => commands.push(payload) }),
- },
- }).default.setup({}, { expose() {} });
- state.address.value = { address: '企业地址' };
- state.agreed.value = true;
- return { state, session, requests, commands, store, format };
- }
- test('a new server quote refreshes selected price, buckets and SKU without rounding cents', async () => {
- const app = fixture();
- const request = app.state.calculate();
- app.requests[0].resolve(quote());
- await request;
- assert.equal(app.store.cart.items[0].product.name, '新套餐');
- assert.equal(app.store.cart.items[0].product.buckets, 60);
- assert.equal(app.store.cart.items[0].product.price_cents, 69950);
- assert.equal(app.store.cart.items[0].product.liters, null, 'old SKU dimensions are cleared');
- assert.equal(app.format.money(69950), '699.50');
- assert.equal(app.state.canSubmit.value, true);
- app.state.submit();
- assert.equal(app.commands[0].expected_amount_cents, 139900);
- assert.equal(app.commands[0].quote_revision, 'a'.repeat(64));
- });
- test('requote clears the old price immediately and a failed quote cannot be submitted', async () => {
- const app = fixture();
- app.state.quote.value = quote();
- const request = app.state.calculate();
- assert.equal(app.state.quote.value, null);
- app.state.submit();
- assert.equal(app.commands.length, 0);
- app.requests[0].reject(new Error('套餐已下架'));
- await request;
- assert.equal(app.state.canSubmit.value, false);
- app.state.submit();
- assert.equal(app.commands.length, 0);
- });
- test('an older quote response cannot overwrite the latest quote', async () => {
- const app = fixture();
- const old = app.state.calculate();
- const latest = app.state.calculate();
- app.requests[1].resolve(quote('最新报价'));
- await latest;
- app.requests[0].resolve(quote('过时报价'));
- await old;
- assert.equal(app.state.quote.value.items[0].name, '最新报价');
- assert.equal(app.store.cart.items[0].product.name, '最新报价');
- });
- test('identity changes invalidate quote and address and reject a late response', async () => {
- const app = fixture();
- app.state.quote.value = quote();
- const request = app.state.calculate();
- app.session.epoch++;
- assert.equal(app.state.address.value, null);
- assert.equal(app.state.quote.value, null);
- app.requests[0].resolve(quote());
- await request;
- assert.equal(app.state.quote.value, null);
- assert.equal(app.store.cart.items[0].product.name, '旧套餐');
- assert.equal(app.state.canSubmit.value, false);
- });
- test('removing the final item invalidates a pending quote and incomplete catalog pages preserve selections', async () => {
- const app = fixture();
- app.store.refreshCartProducts([{ ...product(), id: 2 }]);
- assert.equal(app.store.cart.items.length, 1);
- const request = app.state.calculate();
- app.store.clearCart();
- await app.state.calculate();
- app.requests[0].resolve(quote());
- await request;
- assert.equal(app.state.quote.value, null);
- assert.equal(app.state.canSubmit.value, false);
- });
|