382 lines
12 KiB
TypeScript
382 lines
12 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { fallbackData, formatCampaignPrice, formatHotelPrice, formatProductCardPrice, getSearchProducts, normalizeApiProduct, normalizeDemandRecommendation, normalizeRouteSections, normalizeSiteConfig, productKey, stripTitle } from "@/lib/data";
|
|
|
|
describe("WonderQ shared data helpers", () => {
|
|
it("keeps fallback products searchable by Guizhou aliases", () => {
|
|
const results = getSearchProducts(fallbackData.products, "荔波小七孔");
|
|
|
|
expect(results.length).toBeGreaterThanOrEqual(2);
|
|
expect(results.some((product) => product.title.includes("荔波") || product.tags.includes("荔波"))).toBe(true);
|
|
});
|
|
|
|
it("normalizes API products to the miniapp product shape", () => {
|
|
const product = normalizeApiProduct(
|
|
{
|
|
id: "api-product-42",
|
|
sourceId: null,
|
|
title: "测试贵州线路",
|
|
subtitle: "后台短标题",
|
|
priceAmount: 128000,
|
|
tags: ["小包团"],
|
|
coverImage: null,
|
|
destination: { id: "dest-1", name: "贵阳" },
|
|
},
|
|
3,
|
|
);
|
|
|
|
expect(product.id).toBe(42);
|
|
expect(product.price).toBe("128000");
|
|
expect(product.image).toBe("/assets/guizhou/libo-xiaoqikong.jpg");
|
|
expect(product.subtitle).toBe("后台短标题");
|
|
expect(product.destinationName).toBe("贵阳");
|
|
expect(productKey(product)).toBe("api-product-42");
|
|
});
|
|
|
|
|
|
it("formats product card price without wan conversion", () => {
|
|
expect(formatProductCardPrice("57500")).toBe("¥57500起/人");
|
|
});
|
|
it("normalizes public site map image to app data", () => {
|
|
const siteConfig = normalizeSiteConfig(
|
|
{
|
|
map: [{ id: "map-1", image: "https://cdn.example.test/guizhou-map.webp" }],
|
|
},
|
|
fallbackData,
|
|
);
|
|
|
|
expect(siteConfig.mapImage).toBe("https://cdn.example.test/guizhou-map.webp");
|
|
});
|
|
|
|
it("normalizes destination page hero and region modules", () => {
|
|
const siteConfig = normalizeSiteConfig(
|
|
{
|
|
destinationHero: [
|
|
{
|
|
id: "destination-hero-1",
|
|
title: "后台主视觉标题",
|
|
kicker: "后台小标题",
|
|
image: "https://cdn.example.test/destination-hero.jpg",
|
|
isActive: true,
|
|
sortOrder: 0,
|
|
},
|
|
],
|
|
destinationRegions: [
|
|
{
|
|
id: "region-b",
|
|
label: "黔南",
|
|
keyword: "黔南荔波",
|
|
spots: "荔波小七孔、茂兰",
|
|
isActive: true,
|
|
sortOrder: 2,
|
|
},
|
|
{
|
|
id: "region-a",
|
|
label: "贵阳/安顺",
|
|
keyword: "",
|
|
spots: "甲秀楼、黄果树",
|
|
isActive: true,
|
|
sortOrder: 1,
|
|
},
|
|
],
|
|
},
|
|
fallbackData,
|
|
);
|
|
|
|
expect(siteConfig.destinationHero).toMatchObject({
|
|
id: "destination-hero-1",
|
|
title: "后台主视觉标题",
|
|
kicker: "后台小标题",
|
|
image: "https://cdn.example.test/destination-hero.jpg",
|
|
});
|
|
expect(siteConfig.destinationRegions.map((item) => item.label)).toEqual(["贵阳/安顺", "黔南"]);
|
|
expect(siteConfig.destinationRegions[0].keyword).toBe("贵阳/安顺");
|
|
expect(siteConfig.destinationRegions[1].keyword).toBe("黔南荔波");
|
|
});
|
|
|
|
it("normalizes public hotel and vehicle modules", () => {
|
|
const siteConfig = normalizeSiteConfig(
|
|
{
|
|
hotelGroups: [
|
|
{
|
|
id: "hotel-b",
|
|
title: "野奢酒店升级",
|
|
description: "",
|
|
image: "",
|
|
isActive: true,
|
|
sortOrder: 2,
|
|
},
|
|
{
|
|
id: "hotel-a",
|
|
title: "经典酒店精选",
|
|
description: "接口酒店描述",
|
|
image: "https://cdn.example.test/hotel.jpg",
|
|
coverImage: "https://cdn.example.test/hotel-cover.jpg",
|
|
priceAmount: 68000,
|
|
priceUnit: "起/晚",
|
|
tags: ["温泉", "亲子"],
|
|
status: "published",
|
|
isActive: true,
|
|
sortOrder: 1,
|
|
},
|
|
],
|
|
vehicleOptions: [
|
|
{
|
|
id: "vehicle-1",
|
|
title: "7座舒适商务车",
|
|
description: "接口用车描述",
|
|
image: "https://cdn.example.test/vehicle.jpg",
|
|
isActive: true,
|
|
sortOrder: 0,
|
|
},
|
|
],
|
|
},
|
|
fallbackData,
|
|
);
|
|
|
|
expect(siteConfig.hotelGroups.map((item) => item.title)).toEqual(["经典酒店精选", "野奢酒店升级"]);
|
|
expect(siteConfig.hotelGroups[0]).toMatchObject({
|
|
id: "hotel-a",
|
|
description: "接口酒店描述",
|
|
image: "https://cdn.example.test/hotel-cover.jpg",
|
|
coverImage: "https://cdn.example.test/hotel-cover.jpg",
|
|
priceAmount: 68000,
|
|
priceUnit: "起/晚",
|
|
tags: ["温泉", "亲子"],
|
|
});
|
|
expect(siteConfig.hotelGroups[0].coverImage).toBe("https://cdn.example.test/hotel-cover.jpg");
|
|
expect(formatHotelPrice(siteConfig.hotelGroups[0].priceAmount, siteConfig.hotelGroups[0].priceUnit)).toBe("¥68000起/晚");
|
|
expect(formatHotelPrice(null, null)).toBe("¥107500起/晚");
|
|
expect(siteConfig.hotelGroups[1].image).toBe(fallbackData.hotelGroups[1].image);
|
|
expect(siteConfig.vehicleOptions[0]).toMatchObject({
|
|
id: "vehicle-1",
|
|
title: "7座舒适商务车",
|
|
description: "接口用车描述",
|
|
image: "https://cdn.example.test/vehicle.jpg",
|
|
});
|
|
});
|
|
|
|
it("normalizes demand page modules and recommendation product order", () => {
|
|
const siteConfig = normalizeSiteConfig(
|
|
{
|
|
demandHero: [
|
|
{
|
|
id: "demand-hero-1",
|
|
kicker: "后台 3 步",
|
|
title: "后台需求标题",
|
|
description: "后台需求说明",
|
|
steps: ["提交", "沟通", "确认"],
|
|
isActive: true,
|
|
sortOrder: 0,
|
|
},
|
|
],
|
|
demandFeatureCards: [
|
|
{
|
|
id: "feature-b",
|
|
title: "住宿",
|
|
description: "后台住宿说明",
|
|
isActive: true,
|
|
sortOrder: 2,
|
|
},
|
|
{
|
|
id: "feature-a",
|
|
title: "动线",
|
|
description: "后台动线说明",
|
|
isActive: true,
|
|
sortOrder: 1,
|
|
},
|
|
],
|
|
demandForm: [
|
|
{
|
|
id: "form-1",
|
|
destinationLabel: "想去哪里",
|
|
destinationPlaceholder: "填写目的地",
|
|
phoneLabel: "联系微信",
|
|
phonePlaceholder: "微信号 / 手机号",
|
|
noteLabel: "补充偏好",
|
|
notePlaceholder: "人数、日期、酒店",
|
|
submitLabel: "提交给管家",
|
|
chips: ["荔波", "西江"],
|
|
isActive: true,
|
|
},
|
|
],
|
|
},
|
|
fallbackData,
|
|
);
|
|
const products = [
|
|
normalizeApiProduct({ id: "product-a", sourceId: 1, title: "线路 A", tags: [] }, 0),
|
|
normalizeApiProduct({ id: "product-b", sourceId: 2, title: "线路 B", tags: [] }, 1),
|
|
];
|
|
const recommendation = normalizeDemandRecommendation(
|
|
[
|
|
{
|
|
id: "recommendation-1",
|
|
title: "后台热门",
|
|
subtitle: "后台副文案",
|
|
productIds: ["product-b", "product-a"],
|
|
isActive: true,
|
|
sortOrder: 0,
|
|
},
|
|
],
|
|
products,
|
|
fallbackData.demandRecommendation,
|
|
);
|
|
|
|
expect(siteConfig.demandHero.title).toBe("后台需求标题");
|
|
expect(siteConfig.demandHero.steps).toEqual(["提交", "沟通", "确认"]);
|
|
expect(siteConfig.demandFeatureCards.map((item) => item.title)).toEqual(["动线", "住宿"]);
|
|
expect(siteConfig.demandForm).toMatchObject({
|
|
destinationLabel: "想去哪里",
|
|
submitLabel: "提交给管家",
|
|
chips: ["荔波", "西江"],
|
|
});
|
|
expect(recommendation.title).toBe("后台热门");
|
|
expect(recommendation.products.map((product) => product.title)).toEqual(["线路 B", "线路 A"]);
|
|
});
|
|
|
|
it("falls back to local demand page data when config is missing or productIds do not match", () => {
|
|
const siteConfig = normalizeSiteConfig({}, fallbackData);
|
|
const recommendation = normalizeDemandRecommendation(
|
|
[
|
|
{
|
|
id: "recommendation-empty",
|
|
title: "无匹配推荐",
|
|
productIds: ["missing-product"],
|
|
isActive: true,
|
|
},
|
|
],
|
|
fallbackData.products,
|
|
fallbackData.demandRecommendation,
|
|
);
|
|
|
|
expect(siteConfig.demandHero).toBe(fallbackData.demandHero);
|
|
expect(siteConfig.demandFeatureCards).toBe(fallbackData.demandFeatureCards);
|
|
expect(siteConfig.demandForm).toBe(fallbackData.demandForm);
|
|
expect(recommendation).toBe(fallbackData.demandRecommendation);
|
|
});
|
|
|
|
it("normalizes public campaigns to home deal cards", () => {
|
|
const siteConfig = normalizeSiteConfig(
|
|
{
|
|
campaigns: [
|
|
{
|
|
id: "campaign-1",
|
|
slug: "classic",
|
|
title: "经典打卡特惠",
|
|
description: "接口活动描述",
|
|
coverImage: "https://cdn.example.test/campaign.jpg",
|
|
priceAmount: 57500,
|
|
priceUnit: "起/人",
|
|
},
|
|
],
|
|
},
|
|
fallbackData,
|
|
);
|
|
|
|
expect(siteConfig.campaigns).toHaveLength(1);
|
|
expect(siteConfig.campaigns[0]).toMatchObject({
|
|
id: "campaign-1",
|
|
title: "经典打卡特惠",
|
|
campaignType: "经典打卡特惠",
|
|
image: "https://cdn.example.test/campaign.jpg",
|
|
description: "接口活动描述",
|
|
priceAmount: 57500,
|
|
priceUnit: "起/人",
|
|
tags: ["经典首游", "小包团优惠"],
|
|
});
|
|
expect(formatCampaignPrice(siteConfig.campaigns[0].priceAmount, siteConfig.campaigns[0].priceUnit, siteConfig.campaigns[0].priceText)).toBe("¥57500起/人");
|
|
});
|
|
|
|
it("normalizes public route sections by productIds order", () => {
|
|
const products = [
|
|
normalizeApiProduct(
|
|
{
|
|
id: "product-a",
|
|
sourceId: 1,
|
|
title: "线路 A",
|
|
tags: [],
|
|
},
|
|
0,
|
|
),
|
|
normalizeApiProduct(
|
|
{
|
|
id: "product-b",
|
|
sourceId: 2,
|
|
title: "线路 B",
|
|
tags: [],
|
|
},
|
|
1,
|
|
),
|
|
];
|
|
|
|
const sections = normalizeRouteSections(
|
|
[
|
|
{
|
|
id: "route-section-001",
|
|
title: "后台精选线路",
|
|
subtitle: "后台副文案",
|
|
productIds: ["product-b", "product-a"],
|
|
isActive: true,
|
|
},
|
|
],
|
|
products,
|
|
fallbackData.routeSections,
|
|
);
|
|
|
|
expect(sections).toHaveLength(1);
|
|
expect(sections[0].title).toBe("后台精选线路");
|
|
expect(sections[0].products.map((product) => product.title)).toEqual(["线路 B", "线路 A"]);
|
|
});
|
|
|
|
it("skips inactive route sections and sections without matched products", () => {
|
|
const products = [
|
|
normalizeApiProduct(
|
|
{
|
|
id: "product-visible",
|
|
sourceId: 1,
|
|
title: "线路 A",
|
|
tags: [],
|
|
},
|
|
0,
|
|
),
|
|
];
|
|
|
|
const sections = normalizeRouteSections(
|
|
[
|
|
{
|
|
id: "route-section-inactive",
|
|
title: "停用分组",
|
|
productIds: ["product-visible"],
|
|
isActive: false,
|
|
},
|
|
{
|
|
id: "route-section-empty",
|
|
title: "无匹配商品分组",
|
|
productIds: ["missing-product"],
|
|
isActive: true,
|
|
},
|
|
{
|
|
id: "route-section-active",
|
|
title: "有效分组",
|
|
productIds: ["product-visible"],
|
|
isActive: true,
|
|
},
|
|
],
|
|
products,
|
|
fallbackData.routeSections,
|
|
);
|
|
|
|
expect(sections).toHaveLength(1);
|
|
expect(sections[0].id).toBe("route-section-active");
|
|
expect(sections[0].products.map((product) => product.title)).toEqual(["线路 A"]);
|
|
});
|
|
|
|
it("falls back to local route sections when public route sections are empty", () => {
|
|
expect(normalizeRouteSections([], fallbackData.products, fallbackData.routeSections)).toBe(fallbackData.routeSections);
|
|
});
|
|
|
|
it("strips source brackets from product display titles", () => {
|
|
expect(stripTitle("【贵州】黄果树·荔波小七孔5日")).toBe("贵州黄果树");
|
|
});
|
|
});
|
|
|