feat(core-flows,product,types): scoped variant images (#13623)
* wip(product): variant images * fix: return type * wip: repo and list approach * fix: redo repo method, make test pass * fix: change getVariantImages impl * feat: update test * feat: API and core flows layer * wip: integration spec * fix: deterministic test * chore: refactor and simplify, cleanup, remove repo method * wip: batch add all images to all vairants * fix: remove, expand testing * refactor: pass variants instead of refetch * chore: expand integration test * feat: test multi assign route * fix: remove `/admin/products/:id/variants/images` route * feat: batch images to variant endpoint * fix: length assertion * feat: variant thumbnail * fix: send variant thumbnail by default * fix: product export test assertion * fix: test * feat: variant thumbnail on line item * fix: add missing list and count method, update types * feat: optimise variant images lookups * feat: thumbnail management in core flows * fix: typos, type, build * feat: cascade delete to pivot table, rm unused unused fields * feat(dashboard): variant images management UI (#13670) * wip(dashboard): setup variant media form * wip: cleanup table and images, wip check handler * feat: proper sidebar functionallity * fefat: add js-sdk and hooks * feat: allow only one selection * wip: lazy load variants in the table * feat: new variants management for images on product details * chore: refactor * wip: variant details page work * fix: cleanup media section, fix issues and types * feat: correct scoped images, cleanup in edit modal * feat: js sdk and hooks, filter out product images on variant details, labels, add API call and wrap UI * chore: cleanup * refacto: rename route * feat: thumbnail functionallity * fix: refresh checked after revalidation load * fix: rm unused, refactor type * Create thirty-clocks-refuse.md * feat: new add remove variant media layout * feat: new image add UX --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> * fix: table name in migration * chore: update changesets --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
@@ -1565,6 +1565,146 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
})
|
||||
|
||||
it("should add one item with variant thumbnail and one item with product thumbnail", async () => {
|
||||
const salesChannel = await scModuleService.createSalesChannels({
|
||||
name: "Webshop",
|
||||
})
|
||||
|
||||
const location = await stockLocationModule.createStockLocations({
|
||||
name: "Warehouse",
|
||||
})
|
||||
|
||||
let cart = await cartModuleService.createCarts({
|
||||
currency_code: "usd",
|
||||
sales_channel_id: salesChannel.id,
|
||||
})
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
[Modules.SALES_CHANNEL]: {
|
||||
sales_channel_id: salesChannel.id,
|
||||
},
|
||||
[Modules.STOCK_LOCATION]: {
|
||||
stock_location_id: location.id,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const [product1, product2] = await productModule.createProducts([
|
||||
{
|
||||
title: "Test product 1",
|
||||
status: ProductStatus.PUBLISHED,
|
||||
thumbnail: "product-thumbnail-1",
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant 1",
|
||||
manage_inventory: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Test product 2",
|
||||
status: ProductStatus.PUBLISHED,
|
||||
thumbnail: "product-thumbnail-2",
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant 2",
|
||||
manage_inventory: false,
|
||||
thumbnail: "variant-thumbnail-2",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const priceSet1 = await pricingModule.createPriceSets({
|
||||
prices: [
|
||||
{
|
||||
amount: 30,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const priceSet2 = await pricingModule.createPriceSets({
|
||||
prices: [
|
||||
{
|
||||
amount: 30,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await pricingModule.createPricePreferences({
|
||||
attribute: "currency_code",
|
||||
value: "usd",
|
||||
is_tax_inclusive: true,
|
||||
})
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
[Modules.PRODUCT]: {
|
||||
variant_id: product1.variants[0].id,
|
||||
},
|
||||
[Modules.PRICING]: {
|
||||
price_set_id: priceSet1.id,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
[Modules.PRODUCT]: {
|
||||
variant_id: product2.variants[0].id,
|
||||
},
|
||||
[Modules.PRICING]: {
|
||||
price_set_id: priceSet2.id,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
cart = await cartModuleService.retrieveCart(cart.id, {
|
||||
select: ["id", "region_id", "currency_code", "sales_channel_id"],
|
||||
})
|
||||
|
||||
await addToCartWorkflow(appContainer).run({
|
||||
input: {
|
||||
items: [
|
||||
{
|
||||
variant_id: product1.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
variant_id: product2.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
cart_id: cart.id,
|
||||
},
|
||||
})
|
||||
|
||||
cart = await cartModuleService.retrieveCart(cart.id, {
|
||||
relations: ["items"],
|
||||
})
|
||||
|
||||
expect(cart.items).toHaveLength(2)
|
||||
expect(cart.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
variant_id: product1.variants.find(
|
||||
(v) => v.title === "Test variant 1"
|
||||
)!.id,
|
||||
thumbnail: "product-thumbnail-1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
variant_id: product2.variants.find(
|
||||
(v) => v.title === "Test variant 2"
|
||||
)!.id,
|
||||
thumbnail: "variant-thumbnail-2",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should add custom item to cart", async () => {
|
||||
const salesChannel = await scModuleService.createSalesChannels({
|
||||
name: "Webshop",
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import {
|
||||
batchImageVariantsWorkflow,
|
||||
batchVariantImagesWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { Modules } from "@medusajs/utils"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
const env = {}
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
env,
|
||||
testSuite: ({ getContainer }) => {
|
||||
describe("Workflows: Batch variant image management", () => {
|
||||
let appContainer
|
||||
let productModule: IProductModuleService
|
||||
|
||||
beforeAll(async () => {
|
||||
appContainer = getContainer()
|
||||
productModule = appContainer.resolve(Modules.PRODUCT)
|
||||
})
|
||||
|
||||
const createVariantWithImage = async (
|
||||
imageUrl: string,
|
||||
suffix: string
|
||||
) => {
|
||||
const [createdProduct] = await productModule.createProducts([
|
||||
{
|
||||
title: `test-product-${suffix}`,
|
||||
images: [{ url: imageUrl }],
|
||||
variants: [
|
||||
{
|
||||
title: `test-variant-${suffix}`,
|
||||
sku: `test-variant-sku-${suffix}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const [product] = await productModule.listProducts(
|
||||
{ id: createdProduct.id },
|
||||
{
|
||||
relations: ["variants", "images"],
|
||||
}
|
||||
)
|
||||
|
||||
const variant = product.variants[0]!
|
||||
const image = product.images[0]!
|
||||
|
||||
await productModule.updateProductVariants(variant.id, {
|
||||
thumbnail: imageUrl,
|
||||
})
|
||||
|
||||
await productModule.addImageToVariant([
|
||||
{
|
||||
variant_id: variant.id,
|
||||
image_id: image.id,
|
||||
},
|
||||
])
|
||||
|
||||
const [variantWithThumbnail] = await productModule.listProductVariants(
|
||||
{ id: variant.id },
|
||||
{
|
||||
select: ["id", "thumbnail"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(variantWithThumbnail.thumbnail).toEqual(imageUrl)
|
||||
|
||||
return {
|
||||
variantId: variant.id,
|
||||
imageId: image.id,
|
||||
imageUrl,
|
||||
}
|
||||
}
|
||||
|
||||
it("clears the variant thumbnail when removing images via batchVariantImagesWorkflow", async () => {
|
||||
const imageUrl = "https://test-image-url.com/image-1.png"
|
||||
const { variantId, imageId } = await createVariantWithImage(
|
||||
imageUrl,
|
||||
"variant-workflow"
|
||||
)
|
||||
|
||||
const workflow = batchVariantImagesWorkflow(appContainer)
|
||||
const { result } = await workflow.run({
|
||||
input: {
|
||||
variant_id: variantId,
|
||||
remove: [imageId],
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.removed).toEqual([imageId])
|
||||
|
||||
const [updatedVariant] = await productModule.listProductVariants(
|
||||
{ id: variantId },
|
||||
{
|
||||
select: ["id", "thumbnail"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(updatedVariant.thumbnail).toBeNull()
|
||||
})
|
||||
|
||||
it("clears the variant thumbnail when removing variants via batchImageVariantsWorkflow", async () => {
|
||||
const imageUrl = "https://test-image-url.com/image-2.png"
|
||||
const { variantId, imageId } = await createVariantWithImage(
|
||||
imageUrl,
|
||||
"image-workflow"
|
||||
)
|
||||
|
||||
const workflow = batchImageVariantsWorkflow(appContainer)
|
||||
const { result } = await workflow.run({
|
||||
input: {
|
||||
image_id: imageId,
|
||||
remove: [variantId],
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.removed).toEqual([variantId])
|
||||
|
||||
const [updatedVariant] = await productModule.listProductVariants(
|
||||
{ id: variantId },
|
||||
{
|
||||
select: ["id", "thumbnail"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(updatedVariant.thumbnail).toBeNull()
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user