整个课程和数据UI重新设计版本

This commit is contained in:
shenlei
2026-09-18 17:55:44 +09:00
parent febfd30f49
commit 0a4f97d105
154 changed files with 12366 additions and 4784 deletions
+73 -65
View File
@@ -37,7 +37,10 @@ void main() {
expect(recovered.username, 'alice');
expect(recovered.userId, 'u-1');
expect(recovered.isLoggedIn, isTrue);
expect(recovered.lastSyncTime?.toIso8601String(), '2026-09-16T08:00:00.000Z');
expect(
recovered.lastSyncTime?.toIso8601String(),
'2026-09-16T08:00:00.000Z',
);
expect(recovered.autoSyncEnabled, isTrue);
});
@@ -65,7 +68,7 @@ void main() {
'attempts': 3,
'payload': {'label': 'I need coffee'},
'updated_at': '2026-09-16T10:00:00Z',
}
},
],
'profile': {
'onboarding_complete': true,
@@ -75,7 +78,7 @@ void main() {
'show_chinese_hints': true,
'updated_at': '2026-09-16T10:00:00Z',
},
}
},
};
final response = SyncPullResponse.fromJson(pullJson);
@@ -110,63 +113,66 @@ void main() {
expect(item.status, 'recall');
});
test('applyPullResponse merges lessons by union and upgrades mastery by max checkpoint', () {
final state = AppState();
state.completedLessonIds.add('a0-01');
state.completedLessons = 1;
state.mastery['A0-P01'] = const MasteryItem(
id: 'A0-P01',
label: 'I am Shen',
status: MasteryStatus.recognize,
checkpoint: 1,
evidence: [],
);
test(
'applyPullResponse merges lessons by union and upgrades mastery by max checkpoint',
() {
final state = AppState();
state.completedLessonIds.add('a0-01');
state.completedLessons = 1;
state.mastery['A0-P01'] = const MasteryItem(
id: 'A0-P01',
label: 'I am Shen',
status: MasteryStatus.recognize,
checkpoint: 1,
evidence: [],
);
final remotePull = SyncPullResponse(
serverTime: '2026-09-16T10:00:00Z',
progress: const SyncProgressPayload(
activeLessonId: 'a0-03',
completedLessonIds: ['a0-01', 'a0-02'],
completedSegmentIds: ['a0-01-s1', 'a0-02-s1'],
updatedAt: '2026-09-16T10:00:00Z',
),
masteryUpdates: [
const SyncMasteryItemPayload(
itemId: 'A0-P01',
checkpoint: 3,
status: 'use',
dueAt: '2026-09-20T10:00:00Z',
payload: {'label': 'I am Shen'},
final remotePull = SyncPullResponse(
serverTime: '2026-09-16T10:00:00Z',
progress: const SyncProgressPayload(
activeLessonId: 'a0-03',
completedLessonIds: ['a0-01', 'a0-02'],
completedSegmentIds: ['a0-01-s1', 'a0-02-s1'],
updatedAt: '2026-09-16T10:00:00Z',
),
const SyncMasteryItemPayload(
itemId: 'A0-P02',
checkpoint: 1,
status: 'recognize',
dueAt: '2026-09-18T10:00:00Z',
payload: {'label': 'Thank you'},
masteryUpdates: [
const SyncMasteryItemPayload(
itemId: 'A0-P01',
checkpoint: 3,
status: 'use',
dueAt: '2026-09-20T10:00:00Z',
payload: {'label': 'I am Shen'},
updatedAt: '2026-09-16T10:00:00Z',
),
const SyncMasteryItemPayload(
itemId: 'A0-P02',
checkpoint: 1,
status: 'recognize',
dueAt: '2026-09-18T10:00:00Z',
payload: {'label': 'Thank you'},
updatedAt: '2026-09-16T10:00:00Z',
),
],
profile: const SyncProfilePayload(
onboardingComplete: true,
goal: 'dailyLife',
updatedAt: '2026-09-16T10:00:00Z',
),
],
profile: const SyncProfilePayload(
onboardingComplete: true,
goal: 'dailyLife',
updatedAt: '2026-09-16T10:00:00Z',
),
);
);
final changed = SyncMerger.applyPullResponse(state, remotePull);
expect(changed, isTrue);
// Lesson union
expect(state.completedLessonIds, containsAll(['a0-01', 'a0-02']));
expect(state.completedLessons, 2);
// Mastery max checkpoint upgrade
expect(state.mastery['A0-P01']?.checkpoint, 3);
expect(state.mastery['A0-P01']?.status, MasteryStatus.use);
// New item added from remote
expect(state.mastery['A0-P02']?.checkpoint, 1);
expect(state.mastery['A0-P02']?.label, 'Thank you');
});
final changed = SyncMerger.applyPullResponse(state, remotePull);
expect(changed, isTrue);
// Lesson union
expect(state.completedLessonIds, containsAll(['a0-01', 'a0-02']));
expect(state.completedLessons, 2);
// Mastery max checkpoint upgrade
expect(state.mastery['A0-P01']?.checkpoint, 3);
expect(state.mastery['A0-P01']?.status, MasteryStatus.use);
// New item added from remote
expect(state.mastery['A0-P02']?.checkpoint, 1);
expect(state.mastery['A0-P02']?.label, 'Thank you');
},
);
});
group('SyncMerger restores home page position on a fresh device', () {
@@ -238,7 +244,12 @@ void main() {
progress: SyncProgressPayload(
activeLessonId: 'a0-04',
completedLessonIds: const ['a0-01', 'a0-02', 'a0-03'],
completedSegmentIds: const ['a0-01-a', 'a0-02-a', 'a0-03-a', 'a0-04-a'],
completedSegmentIds: const [
'a0-01-a',
'a0-02-a',
'a0-03-a',
'a0-04-a',
],
updatedAt: '2026-09-16T07:54:27Z',
),
);
@@ -255,10 +266,7 @@ void main() {
SyncMerger.applyPullResponse(state, stalePull());
final review = state.reviewQueue.singleWhere((r) => r.id == 'A0-P03');
expect(
review.dueAt.toUtc(),
DateTime.parse('2026-09-17T07:49:45Z'),
);
expect(review.dueAt.toUtc(), DateTime.parse('2026-09-17T07:49:45Z'));
expect(state.mastery['A0-P03']?.status, MasteryStatus.recall);
});
@@ -320,7 +328,7 @@ void main() {
'username': 'shen',
'token': 'jwt_mock_token_abc',
'expires_in': 604800,
}
},
}),
200,
);
@@ -366,7 +374,7 @@ void main() {
},
'mastery_updates': [],
'profile': null,
}
},
}),
200,
);
@@ -376,7 +384,7 @@ void main() {
jsonEncode({
'code': 0,
'message': 'success',
'data': {'server_time': '2026-09-16T12:05:00Z'}
'data': {'server_time': '2026-09-16T12:05:00Z'},
}),
200,
);
@@ -413,7 +421,7 @@ void main() {
'username': 'tester',
'token': 'auth_token_999',
'expires_in': 604800,
}
},
}),
200,
);
@@ -434,7 +442,7 @@ void main() {
},
'mastery_updates': [],
'profile': null,
}
},
}),
200,
);
@@ -443,7 +451,7 @@ void main() {
jsonEncode({
'code': 0,
'message': 'success',
'data': {'server_time': '2026-09-16T15:00:01Z'}
'data': {'server_time': '2026-09-16T15:00:01Z'},
}),
200,
);