1- //! card_generate 单测(AAA 模式;golden 用例覆盖两类卡源)。
1+ //! card_generate 单测(AAA 模式;golden 用例覆盖两类卡源 + 内容分型 REQ-199 )。
22
3- use crate :: card_generate:: { card_from_fragment, cards_from_note} ;
3+ use crate :: card_generate:: { card_from_fragment, cards_from_note, KIND_ACTION , KIND_FACT } ;
44
55/// 真实笔记形态(structure_note 产出口径:章节 + 正文 + 词汇表块)。
66const NOTE_WITH_GLOSSARY : & str = "# 微积分精讲\n \n ## 第一章 极限 [00:00]\n \n \
@@ -14,11 +14,12 @@ const NOTE_WITH_GLOSSARY: &str = "# 微积分精讲\n\n## 第一章 极限 [00:0
1414fn note_glossary_terms_become_cards ( ) {
1515 // Act
1616 let cards = cards_from_note ( NOTE_WITH_GLOSSARY ) ;
17- // Assert:三术语各一卡,front=术语
17+ // Assert:三术语各一卡,front=术语,kind 恒 fact(词汇表是知识不是操作)
1818 assert_eq ! ( cards. len( ) , 3 ) ;
1919 assert_eq ! ( cards[ 0 ] . front, "极限" ) ;
2020 assert_eq ! ( cards[ 1 ] . front, "导数" ) ;
2121 assert_eq ! ( cards[ 2 ] . front, "孤立术语" ) ;
22+ assert ! ( cards. iter( ) . all( |c| c. kind == KIND_FACT ) ) ;
2223}
2324
2425#[ test]
@@ -48,14 +49,15 @@ fn note_without_glossary_yields_no_cards() {
4849
4950#[ test]
5051fn multi_sentence_fragment_becomes_card ( ) {
51- // Arrange:两句话的碎片
52+ // Arrange:两句话的碎片(无步骤信号)
5253 let text = "眼影晕染要用松软的刷子。少量多次才不会显脏。" ;
5354 // Act
5455 let card = card_from_fragment ( text) ;
55- // Assert:首句线索 + 全文验证
56+ // Assert:首句线索 + 全文验证 + kind 恒 fact
5657 let card = card. expect ( "多句碎片应出卡" ) ;
5758 assert_eq ! ( card. front, "眼影晕染要用松软的刷子" ) ;
5859 assert ! ( card. back. contains( "少量多次" ) ) ;
60+ assert_eq ! ( card. kind, KIND_FACT ) ;
5961}
6062
6163#[ test]
@@ -76,3 +78,50 @@ fn fragment_front_truncated_to_limit() {
7678 // Assert:front 上限 60 字符(UI 可读性)
7779 assert ! ( card. front. chars( ) . count( ) <= 60 ) ;
7880}
81+
82+ // ── 内容分型(REQ-199):步骤信号 → action 卡 ──
83+
84+ #[ test]
85+ fn step_signal_fragment_becomes_action_card ( ) {
86+ // Arrange:含"第一步/第二步"步骤元词的碎片
87+ let text = "修眉的做法。第一步用眉梳梳顺。第二步按眉骨定三点。第三步连点成线。" ;
88+ // Act
89+ let card = card_from_fragment ( text) . expect ( "出卡" ) ;
90+ // Assert:kind=action;front=首句(动作名);back=编号步骤清单(全句入列)
91+ assert_eq ! ( card. kind, KIND_ACTION ) ;
92+ assert_eq ! ( card. front, "修眉的做法" ) ;
93+ assert ! ( card. back. starts_with( "1. 修眉的做法" ) ) ;
94+ assert ! ( card. back. contains( "2. 第一步用眉梳梳顺" ) ) ;
95+ assert ! ( card. back. contains( "3. 第二步按眉骨定三点" ) ) ;
96+ }
97+
98+ #[ test]
99+ fn pair_signal_xi_ranhou_counts_as_action ( ) {
100+ // Arrange:"先…然后"成对出现(隐含操作顺序,无元词)
101+ let text = "补妆先吸走浮粉。然后用粉饼按压。最后定妆喷雾收尾。" ;
102+ // Act
103+ let card = card_from_fragment ( text) . expect ( "出卡" ) ;
104+ // Assert:成对信号 → action
105+ assert_eq ! ( card. kind, KIND_ACTION ) ;
106+ assert ! ( card. back. contains( "3. 最后定妆喷雾收尾" ) ) ;
107+ }
108+
109+ #[ test]
110+ fn lone_ranhou_is_not_action_signal ( ) {
111+ // Arrange:单"然后"无"先"——普通叙事不是操作流程
112+ let text = "昨天学了新技巧。然后去吃了午饭。今天继续练习。" ;
113+ // Act
114+ let card = card_from_fragment ( text) . expect ( "出卡" ) ;
115+ // Assert:维持 fact(防叙事误判伪装动作卡)
116+ assert_eq ! ( card. kind, KIND_FACT ) ;
117+ // back 维持全文验证(非编号清单)
118+ assert ! ( !card. back. starts_with( "1. " ) ) ;
119+ }
120+
121+ #[ test]
122+ fn single_sentence_with_signal_still_no_card ( ) {
123+ // Arrange:单句含步骤信号——无多句结构,步骤清单无从谈起
124+ let card = card_from_fragment ( "第一步先打开软件" ) ;
125+ // Assert:诚实不出卡(防假燃料优先于分型)
126+ assert ! ( card. is_none( ) ) ;
127+ }
0 commit comments