Skip to content

Commit 47eec11

Browse files
committed
feat: add getPostSources method to ForumPostCollection
1 parent 70c7411 commit 47eec11

File tree

2 files changed

+187
-19
lines changed

2 files changed

+187
-19
lines changed

src/module/forum/forum-post.ts

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,13 @@ export class ForumPost {
7373

7474
return fromPromise(
7575
(async () => {
76-
const result = await this.thread.site.amcRequest([
77-
{
78-
moduleName: 'forum/sub/ForumEditPostFormModule',
79-
threadId: this.thread.id,
80-
postId: this.id,
81-
},
82-
]);
83-
76+
const result = await ForumPostCollection.acquirePostSources(this.thread, [this]);
8477
if (result.isErr()) {
8578
throw result.error;
8679
}
87-
88-
const response = result.value[0];
89-
if (!response) {
90-
throw new NoElementError('Empty response');
91-
}
92-
93-
const $ = cheerio.load(String(response.body ?? ''));
94-
const sourceElem = $("textarea[name='source']");
95-
if (sourceElem.length === 0) {
80+
if (this._source === null) {
9681
throw new NoElementError('Source textarea not found');
9782
}
98-
this._source = sourceElem.text();
9983
return this._source;
10084
})(),
10185
(error) => {
@@ -508,4 +492,60 @@ export class ForumPostCollection extends Array<ForumPost> {
508492
}
509493
);
510494
}
495+
496+
/**
497+
* Internal method to acquire post sources in bulk
498+
*/
499+
static acquirePostSources(
500+
thread: ForumThreadRef,
501+
posts: ForumPost[]
502+
): WikidotResultAsync<ForumPostCollection> {
503+
return fromPromise(
504+
(async () => {
505+
const targetPosts = posts.filter((post) => post._source === null);
506+
507+
if (targetPosts.length === 0) {
508+
return new ForumPostCollection(thread, posts);
509+
}
510+
511+
const result = await thread.site.amcRequest(
512+
targetPosts.map((post) => ({
513+
moduleName: 'forum/sub/ForumEditPostFormModule',
514+
threadId: thread.id,
515+
postId: post.id,
516+
}))
517+
);
518+
519+
if (result.isErr()) {
520+
throw result.error;
521+
}
522+
523+
for (let i = 0; i < targetPosts.length; i++) {
524+
const post = targetPosts[i];
525+
const response = result.value[i];
526+
if (!post || !response) continue;
527+
const $ = cheerio.load(String(response.body ?? ''));
528+
const sourceElem = $("textarea[name='source']");
529+
if (sourceElem.length === 0) {
530+
throw new NoElementError(`Source textarea not found for post: ${post.id}`);
531+
}
532+
post._source = sourceElem.text();
533+
}
534+
535+
return new ForumPostCollection(thread, posts);
536+
})(),
537+
(error) => {
538+
if (error instanceof NoElementError) return error;
539+
return new UnexpectedError(`Failed to acquire post sources: ${String(error)}`);
540+
}
541+
);
542+
}
543+
544+
/**
545+
* Get sources for all posts in the collection
546+
* @returns Result containing the collection (for method chaining)
547+
*/
548+
getPostSources(): WikidotResultAsync<ForumPostCollection> {
549+
return ForumPostCollection.acquirePostSources(this.thread, Array.from(this));
550+
}
511551
}

tests/unit/module/forum.test.ts

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { describe, expect, test } from 'bun:test';
55
import type { Element } from 'domhandler';
66
import { ForumCategory } from '../../../src/module/forum/forum-category';
7-
import { ForumPost } from '../../../src/module/forum/forum-post';
7+
import { ForumPost, ForumPostCollection } from '../../../src/module/forum/forum-post';
88
import { ForumThread } from '../../../src/module/forum/forum-thread';
99
import type { ForumThreadRef, SiteRef } from '../../../src/module/types';
1010
import { User } from '../../../src/module/user/user';
@@ -351,3 +351,131 @@ describe('ForumPost data class', () => {
351351
});
352352
});
353353
});
354+
355+
describe('ForumPostCollection', () => {
356+
describe('getPostSources', () => {
357+
test('can get sources for posts', async () => {
358+
const site = createMockSite();
359+
const threadRef = createMockThreadRef(site);
360+
const createdBy = createMockUser('TestUser');
361+
const mockElement = { type: 'tag', name: 'div' } as Element;
362+
363+
const post1 = new ForumPost({
364+
thread: threadRef,
365+
id: 5001,
366+
title: 'Post 1',
367+
text: '<p>Content 1</p>',
368+
element: mockElement,
369+
createdBy,
370+
createdAt: new Date(),
371+
editedBy: null,
372+
editedAt: null,
373+
parentId: null,
374+
});
375+
376+
const post2 = new ForumPost({
377+
thread: threadRef,
378+
id: 5002,
379+
title: 'Post 2',
380+
text: '<p>Content 2</p>',
381+
element: mockElement,
382+
createdBy,
383+
createdAt: new Date(),
384+
editedBy: null,
385+
editedAt: null,
386+
parentId: null,
387+
});
388+
389+
const collection = new ForumPostCollection(threadRef, [post1, post2]);
390+
391+
const mockResponse1 = {
392+
body: '<textarea name="source">Test source 1</textarea>',
393+
};
394+
const mockResponse2 = {
395+
body: '<textarea name="source">Test source 2</textarea>',
396+
};
397+
398+
site.amcRequest = async () => ({
399+
isErr: () => false,
400+
value: [mockResponse1, mockResponse2],
401+
});
402+
403+
const result = await collection.getPostSources();
404+
405+
expect(result.isOk()).toBe(true);
406+
expect(post1._source).toBe('Test source 1');
407+
expect(post2._source).toBe('Test source 2');
408+
});
409+
410+
test('skips already acquired sources', async () => {
411+
const site = createMockSite();
412+
const threadRef = createMockThreadRef(site);
413+
const createdBy = createMockUser('TestUser');
414+
const mockElement = { type: 'tag', name: 'div' } as Element;
415+
416+
const post1 = new ForumPost({
417+
thread: threadRef,
418+
id: 5001,
419+
title: 'Post 1',
420+
text: '<p>Content 1</p>',
421+
element: mockElement,
422+
createdBy,
423+
createdAt: new Date(),
424+
editedBy: null,
425+
editedAt: null,
426+
parentId: null,
427+
});
428+
429+
post1._source = 'cached source';
430+
431+
const post2 = new ForumPost({
432+
thread: threadRef,
433+
id: 5002,
434+
title: 'Post 2',
435+
text: '<p>Content 2</p>',
436+
element: mockElement,
437+
createdBy,
438+
createdAt: new Date(),
439+
editedBy: null,
440+
editedAt: null,
441+
parentId: null,
442+
});
443+
444+
const collection = new ForumPostCollection(threadRef, [post1, post2]);
445+
446+
const mockResponse = {
447+
body: '<textarea name="source">Test source 2</textarea>',
448+
};
449+
450+
let callCount = 0;
451+
site.amcRequest = async (requests) => {
452+
callCount++;
453+
// Only post2 should be requested
454+
expect(requests.length).toBe(1);
455+
expect(requests[0]?.postId).toBe(5002);
456+
return {
457+
isErr: () => false,
458+
value: [mockResponse],
459+
};
460+
};
461+
462+
const result = await collection.getPostSources();
463+
464+
expect(result.isOk()).toBe(true);
465+
expect(callCount).toBe(1);
466+
expect(post1._source).toBe('cached source');
467+
expect(post2._source).toBe('Test source 2');
468+
});
469+
470+
test('handles empty collection', async () => {
471+
const site = createMockSite();
472+
const threadRef = createMockThreadRef(site);
473+
const collection = new ForumPostCollection(threadRef, []);
474+
475+
const result = await collection.getPostSources();
476+
477+
expect(result.isOk()).toBe(true);
478+
expect(collection.length).toBe(0);
479+
});
480+
});
481+
});

0 commit comments

Comments
 (0)