整个课程和数据UI重新设计版本
This commit is contained in:
@@ -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,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user