feat(app-expo): show memoir chapter draft progress on list cards

Explain invite vs. generating state using MemoirState slots, surface remaining
characters to the display gate, refresh memoir-state on pull, and sync i18n.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-19 11:11:58 +08:00
parent 897f49f2ab
commit 95856ca11a
7 changed files with 201 additions and 13 deletions

View File

@@ -0,0 +1,49 @@
import {
chapterCategoryToInterviewStage,
memoirDraftCharsRemaining,
memoirDraftHasStarted,
MIN_CHAPTER_DISPLAY_CHARS,
resolvedChapterCategory,
} from '@/features/memoir/draft-progress';
describe('draft-progress', () => {
test('chapterCategoryToInterviewStage maps career chapters to career', () => {
expect(chapterCategoryToInterviewStage('career_early')).toBe('career');
expect(chapterCategoryToInterviewStage('career_achievement')).toBe('career');
});
test('chapterCategoryToInterviewStage maps beliefs and summary to belief', () => {
expect(chapterCategoryToInterviewStage('beliefs')).toBe('belief');
expect(chapterCategoryToInterviewStage('summary')).toBe('belief');
});
test('resolvedChapterCategory falls back to order index', () => {
expect(
resolvedChapterCategory({ category: '', orderIndex: 2 }),
).toBe('career_early');
});
test('memoirDraftHasStarted when interview slots have snippet', () => {
const slots = {
childhood: { place: { snippet: '老家在小城', segment_ids: [] } },
};
expect(
memoirDraftHasStarted(slots, 'childhood', 0),
).toBe(true);
});
test('memoirDraftHasStarted when word count positive', () => {
expect(memoirDraftHasStarted({}, 'childhood', 12)).toBe(true);
});
test('memoirDraftCharsRemaining caps at zero', () => {
expect(memoirDraftCharsRemaining(MIN_CHAPTER_DISPLAY_CHARS)).toBe(0);
expect(memoirDraftCharsRemaining(MIN_CHAPTER_DISPLAY_CHARS + 50)).toBe(0);
});
test('memoirDraftCharsRemaining subtracts from threshold', () => {
expect(memoirDraftCharsRemaining(100)).toBe(
MIN_CHAPTER_DISPLAY_CHARS - 100,
);
});
});