cabloy 5.1.141 → 5.1.142
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cabloy-version +1 -1
- package/.github/workflows/playwright-e2e.yml +2 -2
- package/CHANGELOG.md +21 -0
- package/cabloy-docs/frontend/form-guide.md +47 -0
- package/cabloy-docs/frontend/markdown-guide.md +50 -17
- package/cabloy-docs/frontend/route-alias-guide.md +20 -0
- package/cabloy-docs/frontend/scripts.md +9 -11
- package/cabloy-docs/frontend/zova-form-under-the-hood.md +25 -0
- package/cabloy-docs/fullstack/parallel-worktree-environment.md +1 -1
- package/cabloy-docs/fullstack/quickstart.md +5 -4
- package/cabloy-docs/reference/repo-scripts.md +29 -42
- package/e2e/config/playwright.config.ts +34 -0
- package/e2e/scripts/e2e.ts +2 -27
- package/e2e/scripts/runE2e.ts +62 -0
- package/e2e/scripts/runE2eArgs.test.ts +75 -0
- package/e2e/scripts/runE2eArgs.ts +158 -0
- package/e2e/specs/{a-commerce/commerce.spec.ts → a-commerce.spec.ts} +361 -30
- package/e2e/specs/{cabloy-basic/account.spec.ts → account.spec.ts} +1 -2
- package/oxlint.config.ts +3 -0
- package/package.json +3 -9
- package/scripts/upgrade.ts +63 -68
- package/vona/packages-utils/lint/package.json +1 -1
- package/vona/packages-utils/lint/src/oxc/lint.ts +1 -1
- package/vona/packages-utils/lint/src/oxc/lintVue.ts +1 -1
- package/vona/pnpm-lock.yaml +41 -41
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundRecoveryView.tsx +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +31 -15
- package/vona/src/suite/a-home/modules/home-user/package.json +1 -0
- package/vona/src/suite/a-home/modules/home-user/src/service/account.ts +15 -2
- package/vona/src/suite/a-home/modules/home-user/test/accountActivation.test.ts +34 -4
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/package.json +2 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/index.ts +26 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/bean/imageScene.markdown.ts +13 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +23 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/bean/resource/locale/type.ts +1 -0
- package/zova/pnpm-lock.yaml +63 -18
- package/zova/src/front/config/config/config.cabloyBasicWeb.ts +3 -1
- package/zova/src/front/config/config/config.cabloyCommerce.ts +4 -2
- package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +64 -63
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +6 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionRefund/controller.tsx +413 -134
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +76 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +67 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +7 -7
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +46 -9
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +50 -30
- package/zova/src/suite/cabloy-basic/modules/basic-captcha/src/component/formFieldCaptcha/controller.tsx +6 -2
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +9 -15
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/package.json +10 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/index.ts +11 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/locales.ts +7 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +723 -5
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +461 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/en-us.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/zh-cn.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/index.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/codeBlockLanguages.ts +42 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/richTextContentStyle.ts +20 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/test/lib/codeBlockLanguages.test.ts +25 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +1 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +26 -12
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/cli/openapi.config.ts +1 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/payMockMockPayment.ts +26 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/apiSchema/payMockMockPayment.ts +8 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/model/payMockPayment.ts +13 -1
- package/zova/src/suite-vendor/a-pay/package.json +3 -3
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +2 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -6
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
- package/e2e/config/playwright.basic.config.ts +0 -3
- package/e2e/config/playwright.commerce.config.ts +0 -3
- package/e2e/config/playwright.shared.config.ts +0 -38
- package/e2e/scripts/testE2eClean.ts +0 -56
- /package/e2e/specs/{cabloy-basic/basic.spec.ts → cabloy-basic.spec.ts} +0 -0
|
@@ -153,15 +153,19 @@ function createAddressFixture(testInfo: TestInfo): IAddressFixture {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function getAddressTextbox(page: Page, name: string): Locator {
|
|
157
|
+
return page.getByRole('group', { name, exact: true }).getByRole('textbox');
|
|
158
|
+
}
|
|
159
|
+
|
|
156
160
|
async function fillAddressForm(page: Page, fixture: IAddressFixture) {
|
|
157
|
-
await page
|
|
158
|
-
await page
|
|
159
|
-
await page
|
|
160
|
-
await page
|
|
161
|
-
await page
|
|
162
|
-
await page
|
|
163
|
-
await page
|
|
164
|
-
await page
|
|
161
|
+
await getAddressTextbox(page, 'Recipient Name *').fill(fixture.recipientName);
|
|
162
|
+
await getAddressTextbox(page, 'Phone *').fill(fixture.phone);
|
|
163
|
+
await getAddressTextbox(page, 'Country Code *').fill(fixture.countryCode);
|
|
164
|
+
await getAddressTextbox(page, 'Region *').fill(fixture.region);
|
|
165
|
+
await getAddressTextbox(page, 'City *').fill(fixture.city);
|
|
166
|
+
await getAddressTextbox(page, 'Postal Code *').fill(fixture.postalCode);
|
|
167
|
+
await getAddressTextbox(page, 'Address Line 1 *').fill(fixture.addressLine1);
|
|
168
|
+
await getAddressTextbox(page, 'Address Line 2').fill(fixture.addressLine2);
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
async function createAddressThroughCustomerPage(
|
|
@@ -593,8 +597,9 @@ test(
|
|
|
593
597
|
.filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
594
598
|
await card.getByRole('button', { name: 'Edit', exact: true }).click();
|
|
595
599
|
await expect(page.getByRole('heading', { name: 'Add address' })).toHaveCount(0);
|
|
596
|
-
|
|
597
|
-
await
|
|
600
|
+
const cityTextbox = getAddressTextbox(page, 'City *');
|
|
601
|
+
await expect(cityTextbox).toHaveValue(fixture.city);
|
|
602
|
+
await cityTextbox.fill(fixture.updatedCity);
|
|
598
603
|
|
|
599
604
|
const updateResponse = waitForAddressResponse(page, 'PATCH', /\/updateMine\/[^/]+$/);
|
|
600
605
|
const mineResponse = waitForAddressMine(page);
|
|
@@ -707,7 +712,7 @@ test(
|
|
|
707
712
|
const orderHtml = await orderResponse.text();
|
|
708
713
|
expect(orderHtml.toLowerCase()).not.toContain('data-zova-hydrated');
|
|
709
714
|
expect(orderHtml).toContain(`Order #${checkout.orderId}`);
|
|
710
|
-
expect(orderHtml).toContain('
|
|
715
|
+
expect(orderHtml).toContain('Paid · $45.99');
|
|
711
716
|
expect(orderHtml).toContain('USD');
|
|
712
717
|
expect(orderHtml).toContain(fixture.recipientName);
|
|
713
718
|
expect(orderHtml).toContain(fixture.addressLine1);
|
|
@@ -1194,11 +1199,12 @@ test(
|
|
|
1194
1199
|
await page.getByPlaceholder('Reason for refund').fill('E2E refund request');
|
|
1195
1200
|
await page.getByRole('button', { name: 'Request refund', exact: true }).click();
|
|
1196
1201
|
expect((await requestResponse).ok()).toBeTruthy();
|
|
1197
|
-
await expect(page.getByText('
|
|
1202
|
+
await expect(page.getByText('Refund requested · $45.99')).toBeVisible();
|
|
1198
1203
|
|
|
1199
1204
|
const adminContext = await browser.newContext();
|
|
1200
1205
|
const adminPage = await adminContext.newPage();
|
|
1201
1206
|
const adminPageErrors = collectPageErrors(adminPage);
|
|
1207
|
+
const adminConsoleErrors = collectConsoleErrors(adminPage);
|
|
1202
1208
|
try {
|
|
1203
1209
|
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
1204
1210
|
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
@@ -1211,28 +1217,87 @@ test(
|
|
|
1211
1217
|
);
|
|
1212
1218
|
const orderRow = await getAdminOrderRow(adminPage, checkout.orderId);
|
|
1213
1219
|
await expect(orderRow).toBeVisible();
|
|
1214
|
-
await
|
|
1215
|
-
|
|
1220
|
+
await expect(
|
|
1221
|
+
orderRow.getByRole('button', { name: 'Review refund', exact: true }),
|
|
1222
|
+
).toBeVisible();
|
|
1223
|
+
await expect(
|
|
1224
|
+
orderRow.getByRole('button', { name: 'Approve refund', exact: true }),
|
|
1225
|
+
).toHaveCount(0);
|
|
1226
|
+
await expect(
|
|
1227
|
+
orderRow.getByRole('button', { name: 'Reject refund', exact: true }),
|
|
1228
|
+
).toHaveCount(0);
|
|
1229
|
+
await orderRow.getByRole('button', { name: 'Review refund', exact: true }).click();
|
|
1230
|
+
const refundDecisionTitle = adminPage.getByRole('heading', { name: 'Refund decision' });
|
|
1231
|
+
await expect(refundDecisionTitle).toBeVisible();
|
|
1232
|
+
await adminPage.getByPlaceholder('Decision reason').fill('E2E approval');
|
|
1233
|
+
await adminPage.locator('input[name="decisionConfirmed"]').check();
|
|
1216
1234
|
const approveResponse = waitForApiResponse(
|
|
1217
1235
|
adminPage,
|
|
1218
1236
|
'POST',
|
|
1219
1237
|
`/api/commerce/trade/order/${checkout.orderId}/approveRefund`,
|
|
1220
1238
|
);
|
|
1221
|
-
await
|
|
1239
|
+
await adminPage.getByRole('button', { name: 'Approve refund', exact: true }).click();
|
|
1222
1240
|
expect((await approveResponse).ok()).toBeTruthy();
|
|
1241
|
+
await expect(refundDecisionTitle).toHaveCount(0);
|
|
1223
1242
|
await expect(orderRow).toContainText('Refund approved');
|
|
1243
|
+
await expect(
|
|
1244
|
+
orderRow.getByRole('button', { name: 'Execute refund', exact: true }),
|
|
1245
|
+
).toHaveCount(1);
|
|
1246
|
+
await expect(
|
|
1247
|
+
orderRow.getByRole('button', { name: 'Inspect refund recovery', exact: true }),
|
|
1248
|
+
).toHaveCount(0);
|
|
1249
|
+
await expect(orderRow.getByRole('checkbox')).toHaveCount(0);
|
|
1250
|
+
await orderRow.getByRole('button', { name: 'Execute refund', exact: true }).click();
|
|
1251
|
+
const refundExecutionTitle = adminPage.getByRole('heading', { name: 'Refund execution' });
|
|
1252
|
+
await expect(refundExecutionTitle).toBeVisible();
|
|
1253
|
+
const refundExecutionDialog = adminPage
|
|
1254
|
+
.locator('div.card')
|
|
1255
|
+
.filter({ has: refundExecutionTitle });
|
|
1256
|
+
const executionButton = refundExecutionDialog.getByRole('button', {
|
|
1257
|
+
name: 'Execute refund',
|
|
1258
|
+
exact: true,
|
|
1259
|
+
});
|
|
1260
|
+
await expect(executionButton).toBeDisabled();
|
|
1261
|
+
const unavailableRecoveryResponse = waitForApiResponse(
|
|
1262
|
+
adminPage,
|
|
1263
|
+
'GET',
|
|
1264
|
+
`/api/commerce/trade/order/${checkout.orderId}/refundRecovery`,
|
|
1265
|
+
);
|
|
1266
|
+
await refundExecutionDialog
|
|
1267
|
+
.getByRole('button', { name: 'Inspect refund recovery', exact: true })
|
|
1268
|
+
.click();
|
|
1269
|
+
expect((await unavailableRecoveryResponse).status()).toBe(404);
|
|
1270
|
+
await expect(
|
|
1271
|
+
refundExecutionDialog.getByText(
|
|
1272
|
+
'Refund recovery is not available yet. Execute the refund first to create a provider refund submission.',
|
|
1273
|
+
),
|
|
1274
|
+
).toBeVisible();
|
|
1275
|
+
await expect(refundExecutionDialog.getByText(/Provider operation:/)).toHaveCount(0);
|
|
1276
|
+
await expect(refundExecutionDialog.getByPlaceholder('Recovery reason')).toHaveCount(0);
|
|
1277
|
+
await expect(
|
|
1278
|
+
refundExecutionDialog.getByRole('button', {
|
|
1279
|
+
name: 'Reconcile provider refund',
|
|
1280
|
+
exact: true,
|
|
1281
|
+
}),
|
|
1282
|
+
).toHaveCount(0);
|
|
1283
|
+
await expect(
|
|
1284
|
+
refundExecutionDialog.getByRole('button', { name: 'Retry refund', exact: true }),
|
|
1285
|
+
).toHaveCount(0);
|
|
1286
|
+
expect(
|
|
1287
|
+
adminConsoleErrors.filter(error =>
|
|
1288
|
+
/refund recovery is not available|AxiosError/i.test(error),
|
|
1289
|
+
),
|
|
1290
|
+
).toEqual([]);
|
|
1291
|
+
await refundExecutionDialog.locator('input[name="executionConfirmed"]').check();
|
|
1224
1292
|
const executeResponse = waitForApiResponse(
|
|
1225
1293
|
adminPage,
|
|
1226
1294
|
'POST',
|
|
1227
1295
|
`/api/commerce/trade/order/${checkout.orderId}/executeRefund`,
|
|
1228
1296
|
);
|
|
1229
|
-
await
|
|
1297
|
+
await executionButton.click();
|
|
1230
1298
|
const executeResponseValue = await executeResponse;
|
|
1231
1299
|
expect(executeResponseValue.ok()).toBeTruthy();
|
|
1232
1300
|
const executeResult = (await executeResponseValue.json()).data;
|
|
1233
|
-
const authorization = executeResponseValue.request().headers()['authorization'];
|
|
1234
|
-
expect(authorization).toMatch(/^Bearer /);
|
|
1235
|
-
const authHeaders = { Authorization: authorization };
|
|
1236
1301
|
expectTableIdentity(executeResult.refundAttemptId);
|
|
1237
1302
|
expectTableIdentity(executeResult.refundOperationId);
|
|
1238
1303
|
expect([
|
|
@@ -1240,11 +1305,39 @@ test(
|
|
|
1240
1305
|
executeResult.refundState,
|
|
1241
1306
|
executeResult.refundAttemptState,
|
|
1242
1307
|
]).toEqual(['refund_approved', 'approved', 'created']);
|
|
1243
|
-
|
|
1308
|
+
await expect(refundExecutionTitle).toHaveCount(0);
|
|
1309
|
+
await orderRow.getByRole('button', { name: 'Execute refund', exact: true }).click();
|
|
1310
|
+
await expect(refundExecutionTitle).toBeVisible();
|
|
1311
|
+
const recoveryResponse = waitForApiResponse(
|
|
1312
|
+
adminPage,
|
|
1313
|
+
'GET',
|
|
1314
|
+
`/api/commerce/trade/order/${checkout.orderId}/refundRecovery`,
|
|
1315
|
+
);
|
|
1316
|
+
await adminPage
|
|
1317
|
+
.getByRole('button', { name: 'Inspect refund recovery', exact: true })
|
|
1318
|
+
.click();
|
|
1319
|
+
expect((await recoveryResponse).ok()).toBeTruthy();
|
|
1320
|
+
await expect(adminPage.getByText(/Provider operation:/)).toBeVisible();
|
|
1321
|
+
await expect(
|
|
1322
|
+
adminPage.getByText(
|
|
1323
|
+
'A provider refund identifier is available. Wait for the verified provider webhook; do not resubmit or reconcile by querying.',
|
|
1324
|
+
),
|
|
1325
|
+
).toBeVisible();
|
|
1326
|
+
await expect(
|
|
1327
|
+
refundExecutionDialog.getByRole('button', {
|
|
1328
|
+
name: 'Reconcile provider refund',
|
|
1329
|
+
exact: true,
|
|
1330
|
+
}),
|
|
1331
|
+
).toHaveCount(0);
|
|
1332
|
+
const completeRefundResponse = waitForApiResponse(
|
|
1333
|
+
adminPage,
|
|
1334
|
+
'POST',
|
|
1244
1335
|
`/api/pay/mock/payment-session/refund-operation/${executeResult.refundOperationId}/complete`,
|
|
1245
|
-
{ headers: authHeaders, data: { outcome: 'succeeded' } },
|
|
1246
1336
|
);
|
|
1247
|
-
|
|
1337
|
+
await refundExecutionDialog
|
|
1338
|
+
.getByRole('button', { name: 'Complete mock refund', exact: true })
|
|
1339
|
+
.click();
|
|
1340
|
+
expect((await completeRefundResponse).ok()).toBeTruthy();
|
|
1248
1341
|
await expect
|
|
1249
1342
|
.poll(
|
|
1250
1343
|
async () => {
|
|
@@ -1787,8 +1880,9 @@ const product = '${suffix}';
|
|
|
1787
1880
|
|
|
1788
1881
|
==highlighted text==
|
|
1789
1882
|
|
|
1790
|
-
|
|
1791
|
-
|
|
|
1883
|
+
|
|
1884
|
+
| Feature | Value |
|
|
1885
|
+
| -------- | ------- |
|
|
1792
1886
|
| Material | Durable |`;
|
|
1793
1887
|
const adminContext = await browser.newContext();
|
|
1794
1888
|
const adminPage = await adminContext.newPage();
|
|
@@ -1839,19 +1933,67 @@ const product = '${suffix}';
|
|
|
1839
1933
|
expect(adminConsoleErrors).toEqual([]);
|
|
1840
1934
|
const editor = adminPage.locator('[contenteditable="true"]');
|
|
1841
1935
|
await expect(editor).toBeVisible();
|
|
1936
|
+
const toolbar = adminPage.getByRole('toolbar', { name: 'Markdown toolbar' });
|
|
1937
|
+
await expect(toolbar).toBeVisible();
|
|
1938
|
+
const undoButton = toolbar.getByRole('button', { name: 'Undo' });
|
|
1939
|
+
await expect(toolbar.getByRole('combobox', { name: 'Code block language' })).toHaveCount(0);
|
|
1940
|
+
const codeBlockToolbar = adminPage.getByRole('toolbar', { name: 'Code block language' });
|
|
1941
|
+
await expect(codeBlockToolbar).toBeHidden();
|
|
1942
|
+
await expect(toolbar.getByRole('button', { name: 'Bold' })).toHaveAttribute('type', 'button');
|
|
1943
|
+
await expect(undoButton).toBeEnabled();
|
|
1944
|
+
const insertTableButton = toolbar.getByRole('button', { name: 'Insert table' });
|
|
1945
|
+
await expect(insertTableButton).toHaveAttribute('type', 'button');
|
|
1946
|
+
await expect(insertTableButton).toHaveAttribute('aria-haspopup', 'grid');
|
|
1947
|
+
await expect(insertTableButton).toHaveAttribute('aria-expanded', 'false');
|
|
1948
|
+
await insertTableButton.click();
|
|
1949
|
+
const tablePickerDialog = adminPage.locator('#markdown-table-size-picker');
|
|
1950
|
+
const tablePicker = tablePickerDialog.locator('[role="grid"]');
|
|
1951
|
+
await expect(tablePickerDialog).toBeVisible();
|
|
1952
|
+
await expect(tablePicker).toHaveCount(1);
|
|
1953
|
+
await expect(insertTableButton).toHaveAttribute('aria-expanded', 'true');
|
|
1954
|
+
await expect(editor.locator('table')).toHaveCount(1);
|
|
1955
|
+
const tablePickerCell = tablePicker.locator('[data-table-picker-cell="2-3"]');
|
|
1956
|
+
await tablePickerCell.hover();
|
|
1957
|
+
await expect(tablePickerDialog.locator('[aria-live="polite"]')).toHaveText(
|
|
1958
|
+
'Selected table size: 2 × 3',
|
|
1959
|
+
);
|
|
1960
|
+
await expect(tablePicker.locator('[aria-selected="true"]')).toHaveCount(6);
|
|
1961
|
+
await expect(tablePicker.locator('[aria-selected="false"]').first()).toBeVisible();
|
|
1962
|
+
await tablePickerCell.click();
|
|
1963
|
+
await expect(tablePickerDialog).toHaveCount(0);
|
|
1964
|
+
await expect(insertTableButton).toHaveAttribute('aria-expanded', 'false');
|
|
1965
|
+
await expect(editor.locator('table')).toHaveCount(2);
|
|
1966
|
+
await expect(editor.locator('table').last().locator('th')).toHaveCount(3);
|
|
1967
|
+
await expect(editor.locator('table').last().locator('td')).toHaveCount(3);
|
|
1842
1968
|
const editorSurface = editor.locator('..');
|
|
1969
|
+
await editor.locator('h1').click();
|
|
1843
1970
|
await expect(editorSurface).not.toHaveClass(/\bprose\b/);
|
|
1844
1971
|
const editorSurfaceClass = await editorSurface.getAttribute('class');
|
|
1845
1972
|
expect(editorSurfaceClass?.trim()).toBeTruthy();
|
|
1846
|
-
await editorSurface.click({ position: { x: 20, y: 280 } });
|
|
1847
1973
|
await expect(editor).toBeFocused();
|
|
1848
1974
|
await expect(editor.locator('h1')).toHaveText(`Product content ${suffix}`);
|
|
1849
1975
|
await expect(editor.locator('ul:not([data-type="taskList"])')).toHaveCount(1);
|
|
1850
1976
|
await expect(editor.locator('blockquote')).toHaveText('Product quote');
|
|
1851
|
-
|
|
1977
|
+
const codeBlock = editor.locator('pre code');
|
|
1978
|
+
const codeBlockLanguage = codeBlockToolbar.getByRole('combobox', {
|
|
1979
|
+
name: 'Code block language',
|
|
1980
|
+
});
|
|
1981
|
+
await expect(codeBlockToolbar).toBeHidden();
|
|
1982
|
+
await expect(codeBlock).toHaveClass(/\blanguage-ts\b/);
|
|
1983
|
+
await expect(codeBlock).toHaveText(`const product = '${suffix}';`);
|
|
1984
|
+
await expect(codeBlock.locator('span[class*="hljs-"]')).not.toHaveCount(0);
|
|
1985
|
+
await codeBlock.click();
|
|
1986
|
+
await expect(codeBlockToolbar).toBeVisible();
|
|
1987
|
+
await expect(codeBlockLanguage).toBeEnabled();
|
|
1988
|
+
await expect(codeBlockLanguage).toHaveValue('typescript');
|
|
1989
|
+
await codeBlockLanguage.selectOption('python');
|
|
1990
|
+
await expect(codeBlock).toHaveClass(/\blanguage-python\b/);
|
|
1991
|
+
await expect(codeBlock.locator('span[class*="hljs-"]')).not.toHaveCount(0);
|
|
1992
|
+
await editor.locator('h1').click();
|
|
1993
|
+
await expect(codeBlockToolbar).toBeHidden();
|
|
1852
1994
|
await expect(editor.locator('mark')).toHaveText('highlighted text');
|
|
1853
|
-
await expect(editor.locator('table th')).toHaveCount(
|
|
1854
|
-
await expect(editor.locator('table td')).toHaveCount(
|
|
1995
|
+
await expect(editor.locator('table th')).toHaveCount(5);
|
|
1996
|
+
await expect(editor.locator('table td')).toHaveCount(5);
|
|
1855
1997
|
await expect(editor.locator('h1')).toHaveCSS('font-weight', '700');
|
|
1856
1998
|
await expect(editor.locator('ul:not([data-type="taskList"])')).toHaveCSS(
|
|
1857
1999
|
'list-style-type',
|
|
@@ -1864,15 +2006,75 @@ const product = '${suffix}';
|
|
|
1864
2006
|
await expect(editor.locator('blockquote')).toHaveCSS('border-left-width', '4px');
|
|
1865
2007
|
await expect(editor.locator('pre')).toHaveCSS('overflow-x', 'auto');
|
|
1866
2008
|
await expect(editor.locator('table th').first()).toHaveCSS('font-weight', '600');
|
|
1867
|
-
await
|
|
2009
|
+
await editor.locator('h1').selectText();
|
|
2010
|
+
const boldButton = toolbar.getByRole('button', { name: 'Bold' });
|
|
2011
|
+
await boldButton.click();
|
|
2012
|
+
await expect(boldButton).toHaveAttribute('aria-pressed', 'true');
|
|
2013
|
+
await undoButton.click();
|
|
2014
|
+
await expect(boldButton).toHaveAttribute('aria-pressed', 'false');
|
|
2015
|
+
await expect(toolbar.getByRole('button', { name: 'Redo' })).toBeEnabled();
|
|
2016
|
+
|
|
2017
|
+
const tableToolbar = adminPage.getByRole('toolbar', { name: 'Table toolbar' });
|
|
2018
|
+
const insertedTable = editor.locator('table').last();
|
|
2019
|
+
await editor.locator('h1').click();
|
|
2020
|
+
await expect(tableToolbar).toBeHidden();
|
|
2021
|
+
await insertedTable.locator('td').first().click();
|
|
2022
|
+
expect(adminPageErrors).toEqual([]);
|
|
2023
|
+
expect(adminConsoleErrors).toEqual([]);
|
|
2024
|
+
await expect(tableToolbar).toBeVisible();
|
|
2025
|
+
expect(adminPageErrors).toEqual([]);
|
|
2026
|
+
expect(adminConsoleErrors).toEqual([]);
|
|
2027
|
+
await expect(tableToolbar.getByRole('button', { name: 'Add row before' })).toHaveAttribute(
|
|
2028
|
+
'type',
|
|
2029
|
+
'button',
|
|
2030
|
+
);
|
|
2031
|
+
await tableToolbar.getByRole('button', { name: 'Add row before' }).click();
|
|
2032
|
+
await expect(insertedTable.locator('td')).toHaveCount(6);
|
|
2033
|
+
expect(adminPageErrors).toEqual([]);
|
|
2034
|
+
await tableToolbar.getByRole('button', { name: 'Delete row' }).click();
|
|
2035
|
+
await expect(insertedTable.locator('td')).toHaveCount(3);
|
|
2036
|
+
expect(adminPageErrors).toEqual([]);
|
|
2037
|
+
await tableToolbar.getByRole('button', { name: 'Add row after' }).click();
|
|
2038
|
+
await expect(insertedTable.locator('td')).toHaveCount(6);
|
|
2039
|
+
expect(adminPageErrors).toEqual([]);
|
|
2040
|
+
await tableToolbar.getByRole('button', { name: 'Delete row' }).click();
|
|
2041
|
+
await expect(insertedTable.locator('td')).toHaveCount(3);
|
|
2042
|
+
expect(adminPageErrors).toEqual([]);
|
|
2043
|
+
await tableToolbar.getByRole('button', { name: 'Add column before' }).click();
|
|
2044
|
+
await expect(insertedTable.locator('th')).toHaveCount(4);
|
|
2045
|
+
await expect(insertedTable.locator('td')).toHaveCount(4);
|
|
2046
|
+
expect(adminPageErrors).toEqual([]);
|
|
2047
|
+
await tableToolbar.getByRole('button', { name: 'Delete column' }).click();
|
|
2048
|
+
await expect(insertedTable.locator('th')).toHaveCount(3);
|
|
2049
|
+
await expect(insertedTable.locator('td')).toHaveCount(3);
|
|
2050
|
+
expect(adminPageErrors).toEqual([]);
|
|
2051
|
+
await tableToolbar.getByRole('button', { name: 'Add column after' }).click();
|
|
2052
|
+
await expect(insertedTable.locator('th')).toHaveCount(4);
|
|
2053
|
+
await expect(insertedTable.locator('td')).toHaveCount(4);
|
|
2054
|
+
expect(adminPageErrors).toEqual([]);
|
|
2055
|
+
await tableToolbar.getByRole('button', { name: 'Delete column' }).click();
|
|
2056
|
+
await expect(insertedTable.locator('th')).toHaveCount(3);
|
|
2057
|
+
await expect(insertedTable.locator('td')).toHaveCount(3);
|
|
2058
|
+
expect(adminPageErrors).toEqual([]);
|
|
2059
|
+
await tableToolbar.getByRole('button', { name: 'Delete table' }).click();
|
|
2060
|
+
await expect(editor.locator('table')).toHaveCount(1);
|
|
2061
|
+
await expect(tableToolbar).toBeHidden();
|
|
2062
|
+
expect(adminPageErrors).toEqual([]);
|
|
1868
2063
|
|
|
2064
|
+
const productUpdate = waitForApiResponse(
|
|
2065
|
+
adminPage,
|
|
2066
|
+
'PATCH',
|
|
2067
|
+
`${productActionPath}/${productId}`,
|
|
2068
|
+
);
|
|
2069
|
+
await adminPage.getByRole('button', { name: 'Submit', exact: true }).click();
|
|
2070
|
+
await productUpdate;
|
|
1869
2071
|
const productResponseAfterUpdate = await adminPage.request.get(
|
|
1870
2072
|
`${productActionPath}/${productId}`,
|
|
1871
2073
|
{ headers },
|
|
1872
2074
|
);
|
|
1873
2075
|
expect(productResponseAfterUpdate.ok()).toBeTruthy();
|
|
1874
2076
|
expect((await productResponseAfterUpdate.json()).productContentForm.descriptionMarkdown).toBe(
|
|
1875
|
-
markdown,
|
|
2077
|
+
markdown.replace('```ts', '```python'),
|
|
1876
2078
|
);
|
|
1877
2079
|
expect(adminPageErrors).toEqual([]);
|
|
1878
2080
|
} finally {
|
|
@@ -1893,6 +2095,135 @@ const product = '${suffix}';
|
|
|
1893
2095
|
},
|
|
1894
2096
|
);
|
|
1895
2097
|
|
|
2098
|
+
test(
|
|
2099
|
+
'ATP-SPC-02: Product Content link toolbar edits Markdown',
|
|
2100
|
+
{ tag: ['@admin', '@flow', '@product'] },
|
|
2101
|
+
async ({ browser }, testInfo) => {
|
|
2102
|
+
test.setTimeout(180_000);
|
|
2103
|
+
const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
2104
|
+
const categoryName = `E2E Product Link Category ${suffix}`;
|
|
2105
|
+
const productTitle = `E2E Product Link ${suffix}`;
|
|
2106
|
+
const markdown = `# Product content ${suffix}\n\nA product description.`;
|
|
2107
|
+
const adminContext = await browser.newContext();
|
|
2108
|
+
const adminPage = await adminContext.newPage();
|
|
2109
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
2110
|
+
const adminConsoleErrors = collectConsoleErrors(adminPage);
|
|
2111
|
+
const productListPath = '/commerce-admin/rest/resource/commerce-catalog%3Aproduct';
|
|
2112
|
+
const categoryActionPath = '/api/commerce/catalog/category';
|
|
2113
|
+
const productActionPath = '/api/commerce/catalog/product';
|
|
2114
|
+
let categoryId: number | string | undefined;
|
|
2115
|
+
let productId: number | string | undefined;
|
|
2116
|
+
let headers: { Authorization: string } | undefined;
|
|
2117
|
+
try {
|
|
2118
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
2119
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
2120
|
+
const accessToken = (await adminContext.cookies()).find(
|
|
2121
|
+
cookie => cookie.name === 'token',
|
|
2122
|
+
)?.value;
|
|
2123
|
+
expect(accessToken).toBeTruthy();
|
|
2124
|
+
headers = { Authorization: `Bearer ${accessToken}` };
|
|
2125
|
+
|
|
2126
|
+
const categoryResponse = await adminPage.request.post(categoryActionPath, {
|
|
2127
|
+
data: { name: categoryName, published: false },
|
|
2128
|
+
headers,
|
|
2129
|
+
});
|
|
2130
|
+
expect(categoryResponse.ok()).toBeTruthy();
|
|
2131
|
+
categoryId = (await categoryResponse.json()).data;
|
|
2132
|
+
expectTableIdentity(categoryId);
|
|
2133
|
+
|
|
2134
|
+
const productResponse = await adminPage.request.post(productActionPath, {
|
|
2135
|
+
data: {
|
|
2136
|
+
categoryId,
|
|
2137
|
+
title: productTitle,
|
|
2138
|
+
published: false,
|
|
2139
|
+
productContentForm: { descriptionMarkdown: markdown },
|
|
2140
|
+
},
|
|
2141
|
+
headers,
|
|
2142
|
+
});
|
|
2143
|
+
expect(productResponse.ok()).toBeTruthy();
|
|
2144
|
+
productId = (await productResponse.json()).data;
|
|
2145
|
+
expectTableIdentity(productId);
|
|
2146
|
+
|
|
2147
|
+
await adminPage.goto(`${productListPath}/${productId}/edit`, { waitUntil: 'load' });
|
|
2148
|
+
await expect(adminPage.locator('html')).toHaveAttribute(
|
|
2149
|
+
'data-zova-hydrated',
|
|
2150
|
+
'commerceAdmin',
|
|
2151
|
+
);
|
|
2152
|
+
const editor = adminPage.locator('[contenteditable="true"]');
|
|
2153
|
+
await expect(editor).toBeVisible();
|
|
2154
|
+
const toolbar = adminPage.getByRole('toolbar', { name: 'Markdown toolbar' });
|
|
2155
|
+
await expect(toolbar).toBeVisible();
|
|
2156
|
+
const linkButton = toolbar.getByRole('button', { name: 'Link' });
|
|
2157
|
+
await expect(linkButton).toHaveAttribute('type', 'button');
|
|
2158
|
+
await expect(linkButton).toBeDisabled();
|
|
2159
|
+
|
|
2160
|
+
await editor.locator('h1').selectText();
|
|
2161
|
+
await expect(linkButton).toBeEnabled();
|
|
2162
|
+
await linkButton.click();
|
|
2163
|
+
const linkPrompt = adminPage.locator('.fixed.inset-0.z-50');
|
|
2164
|
+
await expect(linkPrompt).toBeVisible();
|
|
2165
|
+
const linkInput = linkPrompt.locator('input[type="text"]');
|
|
2166
|
+
await linkInput.fill('https://example.com/docs');
|
|
2167
|
+
await linkInput.press('Enter');
|
|
2168
|
+
await expect(editor.locator('h1 a[href="https://example.com/docs"]')).toHaveText(
|
|
2169
|
+
`Product content ${suffix}`,
|
|
2170
|
+
);
|
|
2171
|
+
await expect(linkButton).toHaveAttribute('aria-pressed', 'true');
|
|
2172
|
+
await editor.press('ArrowLeft');
|
|
2173
|
+
await expect(linkButton).toHaveAttribute('aria-pressed', 'true');
|
|
2174
|
+
|
|
2175
|
+
await linkButton.click();
|
|
2176
|
+
await expect(linkPrompt).toBeVisible();
|
|
2177
|
+
await expect(linkInput).toHaveValue('https://example.com/docs');
|
|
2178
|
+
await linkInput.fill('https://example.com/updated');
|
|
2179
|
+
await linkPrompt.getByRole('button', { name: 'OK' }).click();
|
|
2180
|
+
await expect(editor.locator('h1 a[href="https://example.com/updated"]')).toHaveText(
|
|
2181
|
+
`Product content ${suffix}`,
|
|
2182
|
+
);
|
|
2183
|
+
|
|
2184
|
+
await linkButton.click();
|
|
2185
|
+
await expect(linkPrompt).toBeVisible();
|
|
2186
|
+
await linkPrompt.getByRole('button', { name: 'Cancel' }).click();
|
|
2187
|
+
await expect(editor.locator('h1 a[href="https://example.com/updated"]')).toHaveText(
|
|
2188
|
+
`Product content ${suffix}`,
|
|
2189
|
+
);
|
|
2190
|
+
|
|
2191
|
+
await editor.locator('h1').selectText();
|
|
2192
|
+
await expect(linkButton).toHaveAttribute('aria-pressed', 'true');
|
|
2193
|
+
await linkButton.click();
|
|
2194
|
+
await expect(linkPrompt).toBeVisible();
|
|
2195
|
+
await linkInput.fill('');
|
|
2196
|
+
await linkInput.press('Enter');
|
|
2197
|
+
await expect(editor.locator('h1 a')).toHaveCount(0);
|
|
2198
|
+
await adminPage.getByRole('button', { name: 'Submit', exact: true }).click();
|
|
2199
|
+
const productResponseAfterLinkRemoval = await adminPage.request.get(
|
|
2200
|
+
`${productActionPath}/${productId}`,
|
|
2201
|
+
{ headers },
|
|
2202
|
+
);
|
|
2203
|
+
expect(productResponseAfterLinkRemoval.ok()).toBeTruthy();
|
|
2204
|
+
expect(
|
|
2205
|
+
(await productResponseAfterLinkRemoval.json()).productContentForm.descriptionMarkdown,
|
|
2206
|
+
).not.toContain('https://example.com/updated');
|
|
2207
|
+
expect(adminPageErrors).toEqual([]);
|
|
2208
|
+
expect(adminConsoleErrors).toEqual([]);
|
|
2209
|
+
} finally {
|
|
2210
|
+
if (productId && headers) {
|
|
2211
|
+
const response = await adminPage.request.delete(`${productActionPath}/${productId}`, {
|
|
2212
|
+
headers,
|
|
2213
|
+
});
|
|
2214
|
+
expect(response.ok()).toBeTruthy();
|
|
2215
|
+
}
|
|
2216
|
+
if (categoryId && headers) {
|
|
2217
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${categoryId}`, {
|
|
2218
|
+
headers,
|
|
2219
|
+
});
|
|
2220
|
+
expect(response.ok()).toBeTruthy();
|
|
2221
|
+
}
|
|
2222
|
+
await adminContext.close().catch(() => {});
|
|
2223
|
+
}
|
|
2224
|
+
},
|
|
2225
|
+
);
|
|
2226
|
+
|
|
1896
2227
|
test(
|
|
1897
2228
|
'ATP-SPC-02: SKU renders semantic Admin currency and lifecycle controls',
|
|
1898
2229
|
{ tag: ['@admin', '@flow', '@sku'] },
|
|
@@ -17,7 +17,7 @@ const accountProfileApiPath = '/api/home/user/account/profile';
|
|
|
17
17
|
const imageUploadApiPath = '/api/image/upload';
|
|
18
18
|
const avatarFixturePath = path.resolve(
|
|
19
19
|
import.meta.dirname,
|
|
20
|
-
'
|
|
20
|
+
'../../zova/src/suite/a-home/modules/home-base/assets/img/avatar_user.png',
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
function waitForApiResponse(page: Page, method: string, path: string) {
|
|
@@ -168,7 +168,6 @@ test(
|
|
|
168
168
|
await page.getByPlaceholder('Your email address').fill('unknown-reset@example.test');
|
|
169
169
|
const captcha = page.getByPlaceholder('Please input captcha');
|
|
170
170
|
await expect(captcha).toHaveValue(/\S+/);
|
|
171
|
-
await captcha.fill(await captcha.inputValue());
|
|
172
171
|
const requestResponse = waitForApiResponse(
|
|
173
172
|
page,
|
|
174
173
|
'POST',
|
package/oxlint.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cabloy",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.142",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A Node.js fullstack framework",
|
|
6
6
|
"keywords": [
|
|
@@ -43,14 +43,8 @@
|
|
|
43
43
|
"build:zova:commerce": "pnpm --dir zova run build:ssr:cabloyCommerceBatch",
|
|
44
44
|
"build:zova:commerce:web": "pnpm --dir zova run build:ssr:cabloyCommerce && pnpm --dir zova run build:rest:cabloyCommerce",
|
|
45
45
|
"build:zova:commerce:admin": "pnpm --dir zova run build:ssr:cabloyCommerceAdmin && pnpm --dir zova run build:rest:cabloyCommerceAdmin",
|
|
46
|
-
"test:e2e
|
|
47
|
-
"test:e2e:
|
|
48
|
-
"test:e2e:basic:admin": "playwright test --config e2e/config/playwright.basic.config.ts --grep @admin",
|
|
49
|
-
"test:e2e:basic:clean": "node e2e/scripts/testE2eClean.ts basic",
|
|
50
|
-
"test:e2e:commerce": "playwright test --config e2e/config/playwright.commerce.config.ts",
|
|
51
|
-
"test:e2e:commerce:web": "playwright test --config e2e/config/playwright.commerce.config.ts --grep @web",
|
|
52
|
-
"test:e2e:commerce:admin": "playwright test --config e2e/config/playwright.commerce.config.ts --grep @admin",
|
|
53
|
-
"test:e2e:commerce:clean": "node e2e/scripts/testE2eClean.ts commerce",
|
|
46
|
+
"test:e2e": "node e2e/scripts/runE2e.ts --clean",
|
|
47
|
+
"test:e2e:fast": "node e2e/scripts/runE2e.ts --fast",
|
|
54
48
|
"start": "cd vona && npm run start",
|
|
55
49
|
"start:one": "cd vona && npm run start:one",
|
|
56
50
|
"test": "cd vona && npm run test",
|