Skip to content

Commit 794dca7

Browse files
committed
Add new tests to verify products with attributes and images are loading
1 parent 5354d0a commit 794dca7

File tree

1 file changed

+277
-9
lines changed

1 file changed

+277
-9
lines changed

Modules/Tests/YosemiteTests/PointOfSale/GRDBObservableDataSourceTests.swift

Lines changed: 277 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -315,15 +315,8 @@ struct GRDBObservableDataSourceTests {
315315
// Then: Should show 5 variations for parent 2 (first page, excluding downloadable)
316316
#expect(sut.variationItems.count == 5)
317317

318-
// Then: hasMoreVariations should be false because we loaded all 5 variations
319-
#expect(sut.hasMoreVariations == false)
320-
321-
// When: Load more for parent 2
322-
let hasMoreBefore = sut.hasMoreVariations
323-
sut.loadMoreVariations()
324-
325-
// Then: Should still not have more (no second page to load)
326-
#expect(hasMoreBefore == false)
318+
// Then: hasMoreVariations should be false because we loaded all 5 variations (exactly one page)
319+
#expect(sut.hasMoreVariations == false, "Parent 2 has exactly 5 variations, should fit in one page")
327320

328321
// Then: Verify downloadable variations were excluded from the items
329322
// Total variations in DB: 3 + 5 + 2 + 3 = 13, but only 5 non-downloadable for parent 2 should be loaded
@@ -379,6 +372,197 @@ struct GRDBObservableDataSourceTests {
379372
#expect(sut.hasMoreVariations == false, "All 8 variations loaded, no more pages")
380373
}
381374

375+
@Test("Products load with associated images and attributes")
376+
func test_products_load_with_images_and_attributes() async throws {
377+
// Given: Products with images and attributes
378+
// Note: Products are ordered alphabetically by name, so "Hoodie" comes before "T-Shirt"
379+
let hoodie = createPersistedProduct(id: 1, name: "Hoodie", type: "variable")
380+
let tshirt = createPersistedProduct(id: 2, name: "T-Shirt", type: "simple")
381+
try await insertProducts([hoodie, tshirt])
382+
383+
// Create images for products
384+
try await insertImage(id: 100, src: "https://example.com/hoodie.jpg", name: "Hoodie Main")
385+
try await insertImage(id: 200, src: "https://example.com/tshirt-front.jpg", name: "T-Shirt Front")
386+
try await insertImage(id: 201, src: "https://example.com/tshirt-back.jpg", name: "T-Shirt Back")
387+
388+
// Link images to products
389+
try await linkImageToProduct(imageID: 100, productID: 1)
390+
try await linkImageToProduct(imageID: 200, productID: 2)
391+
try await linkImageToProduct(imageID: 201, productID: 2)
392+
393+
// Create attributes for products
394+
try await insertProductAttribute(
395+
productID: 1,
396+
remoteAttributeID: 1,
397+
name: "Material",
398+
position: 0,
399+
variation: true,
400+
options: ["Cotton", "Polyester"]
401+
)
402+
try await insertProductAttribute(
403+
productID: 2,
404+
remoteAttributeID: 2,
405+
name: "Color",
406+
position: 0,
407+
options: ["Red", "Blue", "Green"]
408+
)
409+
try await insertProductAttribute(
410+
productID: 2,
411+
remoteAttributeID: 3,
412+
name: "Size",
413+
position: 1,
414+
options: ["S", "M", "L", "XL"]
415+
)
416+
417+
// When: Load products
418+
await waitForProductLoad(expectedCount: 2) {
419+
sut.loadProducts()
420+
}
421+
422+
// Then: Products loaded with correct count
423+
#expect(sut.productItems.count == 2)
424+
425+
// Then: Verify first product (Hoodie - alphabetically first) loads with image
426+
guard case .variableParentProduct(let hoodieItem) = sut.productItems[0] else {
427+
Issue.record("First item should be variable product (Hoodie)")
428+
return
429+
}
430+
431+
#expect(hoodieItem.name == "Hoodie")
432+
#expect(hoodieItem.productImageSource == "https://example.com/hoodie.jpg",
433+
"Should load image via .including()")
434+
#expect(hoodieItem.allAttributes.count == 1, "Should load attributes via .including()")
435+
#expect(hoodieItem.allAttributes.first?.name == "Material")
436+
#expect(hoodieItem.allAttributes.first?.options == ["Cotton", "Polyester"])
437+
438+
// Then: Verify second product (T-Shirt - alphabetically second) loads with image and attributes
439+
guard case .simpleProduct(let tshirtItem) = sut.productItems[1] else {
440+
Issue.record("Second item should be simple product (T-Shirt)")
441+
return
442+
}
443+
444+
#expect(tshirtItem.name == "T-Shirt")
445+
#expect(tshirtItem.productImageSource == "https://example.com/tshirt-front.jpg",
446+
"Should load first image via .including()")
447+
}
448+
449+
@Test("Variations load with associated images")
450+
func test_variations_load_with_images() async throws {
451+
// Given: Variable product with variations that have images
452+
let parent = createPersistedProduct(id: 100, name: "Variable Hoodie", type: "variable")
453+
try await insertProducts([parent])
454+
455+
// Create variations
456+
try await grdbManager.databaseConnection.write { db in
457+
// Variation 1: Red, Small
458+
let variation1 = PersistedProductVariation(
459+
id: 1001,
460+
siteID: siteID,
461+
productID: 100,
462+
sku: "VAR-RED-S",
463+
globalUniqueID: nil,
464+
price: "29.99",
465+
downloadable: false,
466+
fullDescription: nil,
467+
manageStock: false,
468+
stockQuantity: nil,
469+
stockStatusKey: "instock"
470+
)
471+
try variation1.insert(db)
472+
473+
// Variation 2: Blue, Large
474+
let variation2 = PersistedProductVariation(
475+
id: 1002,
476+
siteID: siteID,
477+
productID: 100,
478+
sku: "VAR-BLUE-L",
479+
globalUniqueID: nil,
480+
price: "34.99",
481+
downloadable: false,
482+
fullDescription: nil,
483+
manageStock: false,
484+
stockQuantity: nil,
485+
stockStatusKey: "instock"
486+
)
487+
try variation2.insert(db)
488+
}
489+
490+
// Create and link images for variations
491+
try await insertImage(id: 1001, src: "https://example.com/red-small.jpg", name: "Red Small")
492+
try await insertImage(id: 1002, src: "https://example.com/blue-large.jpg", name: "Blue Large")
493+
try await linkImageToVariation(imageID: 1001, variationID: 1001)
494+
try await linkImageToVariation(imageID: 1002, variationID: 1002)
495+
496+
// Create parent product attributes (for variation name generation)
497+
// Note: variation: true is required for attributes to be included in allAttributes
498+
try await insertProductAttribute(
499+
productID: 100,
500+
remoteAttributeID: 1,
501+
name: "Color",
502+
position: 0,
503+
variation: true,
504+
options: ["Red", "Blue"]
505+
)
506+
try await insertProductAttribute(
507+
productID: 100,
508+
remoteAttributeID: 2,
509+
name: "Size",
510+
position: 1,
511+
variation: true,
512+
options: ["Small", "Large"]
513+
)
514+
515+
// Create attributes for variations
516+
try await insertVariationAttribute(variationID: 1001, remoteAttributeID: 1, name: "Color", option: "Red")
517+
try await insertVariationAttribute(variationID: 1001, remoteAttributeID: 2, name: "Size", option: "Small")
518+
try await insertVariationAttribute(variationID: 1002, remoteAttributeID: 1, name: "Color", option: "Blue")
519+
try await insertVariationAttribute(variationID: 1002, remoteAttributeID: 2, name: "Size", option: "Large")
520+
521+
// Load the parent product first to get its attributes
522+
await waitForProductLoad(expectedCount: 1) {
523+
sut.loadProducts()
524+
}
525+
526+
guard case .variableParentProduct(let loadedParent) = sut.productItems.first else {
527+
Issue.record("Expected variable parent product")
528+
return
529+
}
530+
531+
let posParent = loadedParent
532+
533+
// When: Load variations
534+
await waitForVariationLoad {
535+
sut.loadVariations(for: posParent)
536+
}
537+
538+
// Then: Variations loaded with correct count
539+
#expect(sut.variationItems.count == 2)
540+
541+
// Then: Verify first variation has image and attributes
542+
guard case .variation(let variation1) = sut.variationItems[0] else {
543+
Issue.record("First item should be variation")
544+
return
545+
}
546+
547+
#expect(variation1.price == "29.99")
548+
#expect(variation1.productImageSource == "https://example.com/red-small.jpg",
549+
"Should load variation-specific image via .including()")
550+
#expect(variation1.name.contains("Red"), "Variation name should include 'Red' from attributes loaded via .including()")
551+
#expect(variation1.name.contains("Small"), "Variation name should include 'Small' from attributes loaded via .including()")
552+
553+
// Then: Verify second variation has image and attributes
554+
guard case .variation(let variation2) = sut.variationItems[1] else {
555+
Issue.record("Second item should be variation")
556+
return
557+
}
558+
559+
#expect(variation2.price == "34.99")
560+
#expect(variation2.productImageSource == "https://example.com/blue-large.jpg",
561+
"Should load variation-specific image via .including()")
562+
#expect(variation2.name.contains("Blue"), "Variation name should include 'Blue' from attributes loaded via .including()")
563+
#expect(variation2.name.contains("Large"), "Variation name should include 'Large' from attributes loaded via .including()")
564+
}
565+
382566
// MARK: - Helper Methods
383567

384568
private func waitForProductLoad(expectedCount: Int? = nil, action: () -> Void) async {
@@ -529,4 +713,88 @@ struct GRDBObservableDataSourceTests {
529713
stockStatusKey: "instock"
530714
)
531715
}
716+
717+
// MARK: - Image Helpers
718+
719+
private func insertImage(id: Int64, src: String, name: String?) async throws {
720+
try await grdbManager.databaseConnection.write { db in
721+
let image = PersistedImage(
722+
siteID: siteID,
723+
id: id,
724+
dateCreated: Date(),
725+
dateModified: nil,
726+
src: src,
727+
name: name,
728+
alt: nil
729+
)
730+
try image.insert(db)
731+
}
732+
}
733+
734+
private func linkImageToProduct(imageID: Int64, productID: Int64) async throws {
735+
try await grdbManager.databaseConnection.write { db in
736+
let link = PersistedProductImage(
737+
siteID: siteID,
738+
productID: productID,
739+
imageID: imageID
740+
)
741+
try link.insert(db)
742+
}
743+
}
744+
745+
private func linkImageToVariation(imageID: Int64, variationID: Int64) async throws {
746+
try await grdbManager.databaseConnection.write { db in
747+
let link = PersistedProductVariationImage(
748+
siteID: siteID,
749+
productVariationID: variationID,
750+
imageID: imageID
751+
)
752+
try link.insert(db)
753+
}
754+
}
755+
756+
// MARK: - Attribute Helpers
757+
758+
private func insertProductAttribute(
759+
productID: Int64,
760+
remoteAttributeID: Int64,
761+
name: String,
762+
position: Int64,
763+
variation: Bool = false,
764+
options: [String]
765+
) async throws {
766+
try await grdbManager.databaseConnection.write { db in
767+
var attribute = PersistedProductAttribute(
768+
id: nil,
769+
siteID: siteID,
770+
productID: productID,
771+
remoteAttributeID: remoteAttributeID,
772+
name: name,
773+
position: position,
774+
visible: true,
775+
variation: variation,
776+
options: options
777+
)
778+
try attribute.insert(db)
779+
}
780+
}
781+
782+
private func insertVariationAttribute(
783+
variationID: Int64,
784+
remoteAttributeID: Int64,
785+
name: String,
786+
option: String
787+
) async throws {
788+
try await grdbManager.databaseConnection.write { db in
789+
var attribute = PersistedProductVariationAttribute(
790+
id: nil,
791+
siteID: siteID,
792+
productVariationID: variationID,
793+
remoteAttributeID: remoteAttributeID,
794+
name: name,
795+
option: option
796+
)
797+
try attribute.insert(db)
798+
}
799+
}
532800
}

0 commit comments

Comments
 (0)