@@ -6,6 +6,7 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart';
66import 'package:catalyst_voices_repositories/src/database/catalyst_database.dart' ;
77import 'package:catalyst_voices_repositories/src/database/dao/proposals_v2_dao.dart' ;
88import 'package:catalyst_voices_repositories/src/database/model/document_composite_entity.dart' ;
9+ import 'package:catalyst_voices_repositories/src/database/model/raw_proposal_entity.dart' ;
910import 'package:catalyst_voices_repositories/src/database/table/documents_local_metadata.drift.dart' ;
1011import 'package:catalyst_voices_repositories/src/dto/proposal/proposal_submission_action_dto.dart' ;
1112import 'package:collection/collection.dart' ;
@@ -5886,6 +5887,346 @@ void main() {
58865887 await subscription.cancel ();
58875888 });
58885889 });
5890+
5891+ group ('watchProposal' , () {
5892+ final now = DateTime .now ();
5893+ final earlier = now.subtract (const Duration (hours: 1 ));
5894+ final latest = now.add (const Duration (hours: 1 ));
5895+
5896+ test ('returns null when proposal does not exist' , () async {
5897+ const ref = SignedDocumentRef (id: 'non-existent' , ver: 'v1' );
5898+
5899+ await expectLater (
5900+ dao.watchProposal (id: ref),
5901+ emits (isNull),
5902+ );
5903+ });
5904+
5905+ test ('returns proposal with draft status when no action exists' , () async {
5906+ final author = _createTestAuthor ();
5907+ final proposalVer = _buildUuidV7At (now);
5908+ final proposal = _createTestDocumentEntity (
5909+ id: 'p1' ,
5910+ ver: proposalVer,
5911+ authors: [author],
5912+ );
5913+
5914+ await db.documentsV2Dao.saveAll ([proposal]);
5915+
5916+ final ref = SignedDocumentRef (id: proposal.doc.id, ver: proposal.doc.ver);
5917+
5918+ await expectLater (
5919+ dao.watchProposal (id: ref),
5920+ emits (
5921+ isA <RawProposalEntity ?>()
5922+ .having ((e) => e? .proposal.id, 'proposal.id' , proposal.doc.id)
5923+ .having ((e) => e? .proposal.ver, 'proposal.ver' , proposal.doc.ver)
5924+ .having ((e) => e? .actionType, 'actionType' , ProposalSubmissionAction .draft),
5925+ ),
5926+ );
5927+ });
5928+
5929+ test ('returns proposal with final status when final action exists' , () async {
5930+ final author = _createTestAuthor ();
5931+ final proposalVer = _buildUuidV7At (now);
5932+ final proposal = _createTestDocumentEntity (
5933+ id: proposalVer,
5934+ ver: proposalVer,
5935+ authors: [author],
5936+ );
5937+
5938+ final finalAction = _createTestDocumentEntity (
5939+ id: 'action-1' ,
5940+ ver: _buildUuidV7At (latest),
5941+ type: DocumentType .proposalActionDocument,
5942+ refId: proposal.doc.id,
5943+ refVer: proposal.doc.ver,
5944+ contentData: ProposalSubmissionActionDto .aFinal.toJson (),
5945+ authors: [author],
5946+ );
5947+
5948+ await db.documentsV2Dao.saveAll ([proposal, finalAction]);
5949+
5950+ final ref = SignedDocumentRef (id: proposal.doc.id, ver: proposal.doc.ver);
5951+
5952+ await expectLater (
5953+ dao.watchProposal (id: ref),
5954+ emits (
5955+ isA <RawProposalEntity ?>().having (
5956+ (e) => e? .actionType,
5957+ 'actionType' ,
5958+ ProposalSubmissionAction .aFinal,
5959+ ),
5960+ ),
5961+ );
5962+ });
5963+
5964+ test ('returns proposal with hide status when latest action is hide' , () async {
5965+ final author = _createTestAuthor ();
5966+ final proposalVer = _buildUuidV7At (now);
5967+ final proposal = _createTestDocumentEntity (
5968+ id: proposalVer,
5969+ ver: proposalVer,
5970+ authors: [author],
5971+ );
5972+
5973+ final hideAction = _createTestDocumentEntity (
5974+ id: 'action-1' ,
5975+ ver: _buildUuidV7At (latest),
5976+ type: DocumentType .proposalActionDocument,
5977+ refId: proposal.doc.id,
5978+ refVer: proposal.doc.ver,
5979+ contentData: ProposalSubmissionActionDto .hide.toJson (),
5980+ authors: [author],
5981+ );
5982+
5983+ await db.documentsV2Dao.saveAll ([proposal, hideAction]);
5984+
5985+ final ref = SignedDocumentRef (id: proposal.doc.id, ver: proposal.doc.ver);
5986+
5987+ await expectLater (
5988+ dao.watchProposal (id: ref),
5989+ emits (
5990+ isA <RawProposalEntity ?>().having (
5991+ (e) => e? .actionType,
5992+ 'actionType' ,
5993+ ProposalSubmissionAction .hide,
5994+ ),
5995+ ),
5996+ );
5997+ });
5998+
5999+ test ('returns hide when latest action (any version) is hide' , () async {
6000+ final author = _createTestAuthor ();
6001+ final proposalV1Ver = _buildUuidV7At (earlier);
6002+ final proposalV2Ver = _buildUuidV7At (now);
6003+
6004+ // First version must have id == ver for valid_actions CTE
6005+ final proposalV1 = _createTestDocumentEntity (
6006+ id: proposalV1Ver,
6007+ ver: proposalV1Ver,
6008+ authors: [author],
6009+ );
6010+
6011+ final proposalV2 = _createTestDocumentEntity (
6012+ id: proposalV1Ver,
6013+ ver: proposalV2Ver,
6014+ authors: [author],
6015+ );
6016+
6017+ // Final action on V1
6018+ final finalActionV1 = _createTestDocumentEntity (
6019+ id: 'action-1' ,
6020+ ver: _buildUuidV7At (now),
6021+ type: DocumentType .proposalActionDocument,
6022+ refId: proposalV1.doc.id,
6023+ refVer: proposalV1.doc.ver,
6024+ contentData: ProposalSubmissionActionDto .aFinal.toJson (),
6025+ authors: [author],
6026+ );
6027+
6028+ // Hide action on V2 (latest action overall)
6029+ final hideActionV2 = _createTestDocumentEntity (
6030+ id: 'action-2' ,
6031+ ver: _buildUuidV7At (latest),
6032+ type: DocumentType .proposalActionDocument,
6033+ refId: proposalV2.doc.id,
6034+ refVer: proposalV2.doc.ver,
6035+ contentData: ProposalSubmissionActionDto .hide.toJson (),
6036+ authors: [author],
6037+ );
6038+
6039+ await db.documentsV2Dao.saveAll ([
6040+ proposalV1,
6041+ proposalV2,
6042+ finalActionV1,
6043+ hideActionV2,
6044+ ]);
6045+
6046+ // Query V1 - should return hide because latest action (any ver) is hide
6047+ final ref = SignedDocumentRef (id: proposalV1.doc.id, ver: proposalV1.doc.ver);
6048+
6049+ await expectLater (
6050+ dao.watchProposal (id: ref),
6051+ emits (
6052+ isA <RawProposalEntity ?>().having (
6053+ (e) => e? .actionType,
6054+ 'actionType' ,
6055+ ProposalSubmissionAction .hide,
6056+ ),
6057+ ),
6058+ );
6059+ });
6060+
6061+ test ('includes template when available' , () async {
6062+ final author = _createTestAuthor ();
6063+ const templateRef = SignedDocumentRef (id: 'template-1' , ver: 'template-v1' );
6064+
6065+ final template = _createTestDocumentEntity (
6066+ id: templateRef.id,
6067+ ver: templateRef.ver,
6068+ type: DocumentType .proposalTemplate,
6069+ authors: [author],
6070+ );
6071+
6072+ final proposalVer = _buildUuidV7At (now);
6073+ final proposal = _createTestDocumentEntity (
6074+ id: 'p1' ,
6075+ ver: proposalVer,
6076+ templateId: templateRef.id,
6077+ templateVer: templateRef.ver,
6078+ authors: [author],
6079+ );
6080+
6081+ await db.documentsV2Dao.saveAll ([template, proposal]);
6082+
6083+ final ref = SignedDocumentRef (id: proposal.doc.id, ver: proposal.doc.ver);
6084+
6085+ await expectLater (
6086+ dao.watchProposal (id: ref),
6087+ emits (
6088+ isA <RawProposalEntity ?>()
6089+ .having ((e) => e? .template, 'template' , isNotNull)
6090+ .having ((e) => e? .template? .id, 'template.id' , templateRef.id),
6091+ ),
6092+ );
6093+ });
6094+
6095+ test ('includes favorite status' , () async {
6096+ final author = _createTestAuthor ();
6097+ final proposalVer = _buildUuidV7At (now);
6098+ final proposal = _createTestDocumentEntity (
6099+ id: 'p1' ,
6100+ ver: proposalVer,
6101+ authors: [author],
6102+ );
6103+
6104+ await db.documentsV2Dao.saveAll ([proposal]);
6105+
6106+ // Mark as favorite
6107+ await dao.updateProposalFavorite (id: proposal.doc.id, isFavorite: true );
6108+
6109+ final ref = SignedDocumentRef (id: proposal.doc.id, ver: proposal.doc.ver);
6110+
6111+ await expectLater (
6112+ dao.watchProposal (id: ref),
6113+ emits (
6114+ isA <RawProposalEntity ?>().having ((e) => e? .isFavorite, 'isFavorite' , isTrue),
6115+ ),
6116+ );
6117+ });
6118+
6119+ test ('includes version IDs' , () async {
6120+ final author = _createTestAuthor ();
6121+ final proposalV1Ver = _buildUuidV7At (earlier);
6122+ final proposalV2Ver = _buildUuidV7At (now);
6123+
6124+ final proposalV1 = _createTestDocumentEntity (
6125+ id: 'p1' ,
6126+ ver: proposalV1Ver,
6127+ authors: [author],
6128+ );
6129+
6130+ final proposalV2 = _createTestDocumentEntity (
6131+ id: 'p1' ,
6132+ ver: proposalV2Ver,
6133+ authors: [author],
6134+ );
6135+
6136+ await db.documentsV2Dao.saveAll ([proposalV1, proposalV2]);
6137+
6138+ final ref = SignedDocumentRef (id: proposalV1.doc.id, ver: proposalV1.doc.ver);
6139+
6140+ await expectLater (
6141+ dao.watchProposal (id: ref),
6142+ emits (
6143+ isA <RawProposalEntity ?>()
6144+ .having ((e) => e? .versionIds, 'versionIds' , hasLength (2 ))
6145+ .having ((e) => e? .versionIds, 'versionIds' , contains (proposalV1Ver))
6146+ .having ((e) => e? .versionIds, 'versionIds' , contains (proposalV2Ver)),
6147+ ),
6148+ );
6149+ });
6150+
6151+ test ('includes comments count' , () async {
6152+ final author = _createTestAuthor ();
6153+ final proposalVer = _buildUuidV7At (now);
6154+ final proposal = _createTestDocumentEntity (
6155+ id: 'p1' ,
6156+ ver: proposalVer,
6157+ authors: [author],
6158+ );
6159+
6160+ final comment1 = _createTestDocumentEntity (
6161+ id: 'c1' ,
6162+ ver: _buildUuidV7At (now),
6163+ type: DocumentType .commentDocument,
6164+ refId: proposal.doc.id,
6165+ refVer: proposal.doc.ver,
6166+ authors: [author],
6167+ );
6168+
6169+ final comment2 = _createTestDocumentEntity (
6170+ id: 'c2' ,
6171+ ver: _buildUuidV7At (latest),
6172+ type: DocumentType .commentDocument,
6173+ refId: proposal.doc.id,
6174+ refVer: proposal.doc.ver,
6175+ authors: [author],
6176+ );
6177+
6178+ await db.documentsV2Dao.saveAll ([proposal, comment1, comment2]);
6179+
6180+ final ref = SignedDocumentRef (id: proposal.doc.id, ver: proposal.doc.ver);
6181+
6182+ await expectLater (
6183+ dao.watchProposal (id: ref),
6184+ emits (
6185+ isA <RawProposalEntity ?>().having ((e) => e? .commentsCount, 'commentsCount' , 2 ),
6186+ ),
6187+ );
6188+ });
6189+
6190+ test ('stream emits updated value when data changes' , () async {
6191+ final author = _createTestAuthor ();
6192+ final proposalVer = _buildUuidV7At (now);
6193+ final proposal = _createTestDocumentEntity (
6194+ id: proposalVer,
6195+ ver: proposalVer,
6196+ authors: [author],
6197+ );
6198+
6199+ await db.documentsV2Dao.saveAll ([proposal]);
6200+
6201+ final ref = SignedDocumentRef (id: proposal.doc.id, ver: proposal.doc.ver);
6202+ final emissions = < RawProposalEntity ? > [];
6203+
6204+ final subscription = dao.watchProposal (id: ref).listen (emissions.add);
6205+
6206+ await pumpEventQueue ();
6207+ expect (emissions, hasLength (1 ));
6208+ expect (emissions[0 ]? .actionType, ProposalSubmissionAction .draft);
6209+
6210+ // Add final action
6211+ final finalAction = _createTestDocumentEntity (
6212+ id: 'action-1' ,
6213+ ver: _buildUuidV7At (latest),
6214+ type: DocumentType .proposalActionDocument,
6215+ refId: proposal.doc.id,
6216+ refVer: proposal.doc.ver,
6217+ contentData: ProposalSubmissionActionDto .aFinal.toJson (),
6218+ authors: [author],
6219+ );
6220+
6221+ await db.documentsV2Dao.saveAll ([finalAction]);
6222+ await pumpEventQueue ();
6223+
6224+ expect (emissions, hasLength (2 ));
6225+ expect (emissions[1 ]? .actionType, ProposalSubmissionAction .aFinal);
6226+
6227+ await subscription.cancel ();
6228+ });
6229+ });
58896230 },
58906231 skip: driftSkip,
58916232 );
0 commit comments