From b909ff2c7d5f6a6ee977e2831717e2a017fb2f4d Mon Sep 17 00:00:00 2001 From: Adam Kliment Date: Sun, 1 Jun 2025 10:44:58 +0200 Subject: [PATCH] fix(link): The fix for the broken hasManyThrough relationship with multiple through model instancees (#2008) --- .../attributes/relations/HasManyThrough.ts | 2 +- .../has_many_through_retrieve.spec.ts | 70 ++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/packages/pinia-orm/src/model/attributes/relations/HasManyThrough.ts b/packages/pinia-orm/src/model/attributes/relations/HasManyThrough.ts index 136b44fa8..895af7171 100644 --- a/packages/pinia-orm/src/model/attributes/relations/HasManyThrough.ts +++ b/packages/pinia-orm/src/model/attributes/relations/HasManyThrough.ts @@ -95,7 +95,7 @@ export class HasManyThrough extends Relation { const key = model[this.localKey] dictionary[key] - ? model.$setRelation(relation, dictionary[key][0]) + ? model.$setRelation(relation, dictionary[key].flat().filter(item => item !== undefined)) : model.$setRelation(relation, []) }) } diff --git a/packages/pinia-orm/tests/feature/relations/has_many_through_retrieve.spec.ts b/packages/pinia-orm/tests/feature/relations/has_many_through_retrieve.spec.ts index 0b159ba9e..ba56126e7 100644 --- a/packages/pinia-orm/tests/feature/relations/has_many_through_retrieve.spec.ts +++ b/packages/pinia-orm/tests/feature/relations/has_many_through_retrieve.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { Model, useRepo } from '../../../src' -import { Attr, HasManyThrough, Str } from '../../../src/decorators' +import { Attr, HasMany, HasManyThrough, Str } from '../../../src/decorators' import { assertInstanceOf, assertModel, fillState } from '../../helpers' describe('feature/relations/has_many_through_retrieve', () => { @@ -115,4 +115,72 @@ describe('feature/relations/has_many_through_retrieve', () => { expect(country.posts[0].id).toBe(2) expect(country.posts[1].id).toBe(1) }) + + it('can retrieve multi-level "has many through" relations with manual comparison', () => { + // Extend the Country model with a direct relationship to users to manifest the difference + class ExtendedCountry extends Country { + @HasMany(() => User, 'countryId') + declare users: User[] + } + + // Extend the User model with a direct relationship to posts to manifest the difference + class ExtendedUser extends User { + @HasMany(() => Post, 'userId') + declare posts: Post[] + } + + const countryRepo = useRepo(ExtendedCountry) + + // Set up test data with: + // - 2 countries + // - 3 users (2 in country 1, 1 in country 2) + // - 5 posts (3 from user 1, 1 from user 2, 1 from user 3) + fillState({ + users: { + 1: { id: 1, name: 'John Doe', countryId: 1 }, + 2: { id: 2, name: 'Jane Doe', countryId: 1 }, + 3: { id: 3, name: 'Johnny Doe', countryId: 2 }, + }, + countries: { + 1: { id: 1 }, + 2: { id: 2 }, + }, + posts: { + 1: { id: 1, userId: 1, title: 'Title 01' }, + 2: { id: 2, userId: 1, title: 'Title 02' }, + 3: { id: 3, userId: 1, title: 'Title 03' }, + 4: { id: 4, userId: 2, title: 'Title 04' }, + 5: { id: 5, userId: 3, title: 'Title 05' }, + }, + }) + + // Register the extended user model to make posts accessible + useRepo(ExtendedUser) + + // Retrieve country with all related data + const country = countryRepo.withAllRecursive().first()! + + // Verify the country is retrieved correctly + expect(country.id).toBe(1) + + // Verify users are retrieved correctly + expect(country.users.length).toBe(2) + + // Verify posts are retrieved correctly through HasManyThrough + expect(country.posts.length).toBe(4) // Should include all posts from all users in country 1 + + // Manually retrieve all posts through users to compare + const manualPosts = country.users.flatMap((user) => { + // Cast user to ExtendedUser to access posts + return (user as unknown as ExtendedUser).posts + }) + + // Compare the manual retrieval with the HasManyThrough relationship + // This tests that HasManyThrough correctly retrieves all related models + expect(country.posts.length).toEqual(manualPosts.length) + + // Verify all expected post IDs are present + const postIds = country.posts.map(post => post.id).sort() + expect(postIds).toEqual([1, 2, 3, 4]) + }) })