1use prosaic_core::{Engine, ProsaicError};
2
3pub mod en;
8
9pub mod es;
11
12pub fn register(engine: &mut Engine) -> Result<(), ProsaicError> {
24 en::register(engine)
25}
26
27pub fn register_es(engine: &mut Engine) -> Result<(), ProsaicError> {
32 es::register(engine)
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use prosaic_core::{Context, Engine, Session, Strictness, Value, Variation};
39 use prosaic_grammar_en::English;
40
41 fn test_engine() -> Engine {
42 let mut engine = Engine::new(English::new())
43 .strictness(Strictness::Strict)
44 .variation(Variation::Fixed);
45 register(&mut engine).unwrap();
46 engine
47 }
48
49 #[test]
50 fn rename_event() {
51 let engine = test_engine();
52
53 let mut ctx = Context::new();
54 ctx.insert("entity_type", Value::String("class".into()));
55 ctx.insert("old_name", Value::String("Foo".into()));
56 ctx.insert("new_name", Value::String("Foobar".into()));
57 ctx.insert("consumer_count", Value::Number(6));
58 ctx.insert(
59 "consumers",
60 Value::List(vec![
61 "Baz".into(),
62 "Qux".into(),
63 "Quux".into(),
64 "Corge".into(),
65 "Grault".into(),
66 "Garply".into(),
67 ]),
68 );
69 let mut session = Session::new();
70
71 let result = engine.render(&mut session, "code.renamed", &ctx).unwrap();
72 assert!(result.contains("Foo was renamed to Foobar"));
73 assert!(result.contains("6 direct consumers"));
74 assert!(result.contains("Baz"));
75 assert!(result.contains("Qux"));
76 assert!(result.contains("Quux"));
77 }
78
79 #[test]
80 fn rename_event_single_consumer() {
81 let engine = test_engine();
82
83 let mut ctx = Context::new();
84 ctx.insert("entity_type", Value::String("method".into()));
85 ctx.insert("old_name", Value::String("getData".into()));
86 ctx.insert("new_name", Value::String("fetchData".into()));
87 ctx.insert("consumer_count", Value::Number(1));
88 ctx.insert("consumers", Value::List(vec!["DashboardComponent".into()]));
89 let mut session = Session::new();
90
91 let result = engine.render(&mut session, "code.renamed", &ctx).unwrap();
92 assert!(result.contains("getData"));
95 assert!(result.contains("fetchData"));
96 }
97
98 #[test]
99 fn delete_event() {
100 let engine = test_engine();
101
102 let mut ctx = Context::new();
103 ctx.insert("entity_type", Value::String("interface".into()));
104 ctx.insert("name", Value::String("UserProfile".into()));
105 ctx.insert("consumer_count", Value::Number(3));
106 ctx.insert(
107 "consumers",
108 Value::List(vec![
109 "UserService".into(),
110 "ProfilePage".into(),
111 "AdminPanel".into(),
112 ]),
113 );
114 let mut session = Session::new();
115
116 let result = engine.render(&mut session, "code.deleted", &ctx).unwrap();
117 assert!(result.contains("UserProfile was removed"));
118 assert!(result.contains("3 dependents"));
119 assert!(result.contains("UserService"));
120 assert!(result.contains("ProfilePage"));
121 assert!(result.contains("AdminPanel"));
122 }
123
124 #[test]
125 fn add_event() {
126 let engine = test_engine();
127
128 let mut ctx = Context::new();
129 ctx.insert("entity_type", Value::String("service".into()));
130 ctx.insert("name", Value::String("AuthGuard".into()));
131 ctx.insert("location", Value::String("src/guards/auth.guard.ts".into()));
132 let mut session = Session::new();
133
134 let result = engine.render(&mut session, "code.added", &ctx).unwrap();
135 assert!(result.contains("service AuthGuard"));
136 assert!(result.contains("src/guards/auth.guard.ts"));
137 }
138
139 #[test]
140 fn modify_event() {
141 let engine = test_engine();
142
143 let mut ctx = Context::new();
144 ctx.insert("entity_type", Value::String("method".into()));
145 ctx.insert("name", Value::String("processOrder".into()));
146 ctx.insert("consumer_count", Value::Number(4));
147 ctx.insert(
148 "consumers",
149 Value::List(vec![
150 "OrderPage".into(),
151 "CartService".into(),
152 "CheckoutFlow".into(),
153 "OrderHistory".into(),
154 ]),
155 );
156 let mut session = Session::new();
157
158 let result = engine.render(&mut session, "code.modified", &ctx).unwrap();
159 assert!(result.contains("processOrder was modified"));
160 assert!(result.contains("4 consumers"));
161 assert!(result.contains("OrderPage"));
162 assert!(result.contains("CartService"));
163 assert!(result.contains("CheckoutFlow"));
164 }
165
166 #[test]
167 fn move_event() {
168 let engine = test_engine();
169
170 let mut ctx = Context::new();
171 ctx.insert("entity_type", Value::String("class".into()));
172 ctx.insert("name", Value::String("Logger".into()));
173 ctx.insert("old_location", Value::String("src/utils/logger.ts".into()));
174 ctx.insert("new_location", Value::String("src/core/logger.ts".into()));
175 ctx.insert("consumer_count", Value::Number(12));
176 ctx.insert(
177 "consumers",
178 Value::List(vec![
179 "AppModule".into(),
180 "AuthService".into(),
181 "UserService".into(),
182 "OrderService".into(),
183 ]),
184 );
185 let mut session = Session::new();
186
187 let result = engine.render(&mut session, "code.moved", &ctx).unwrap();
188 assert!(result.contains("Logger"));
189 assert!(result.contains("src/utils/logger.ts"));
190 assert!(result.contains("src/core/logger.ts"));
191 assert!(result.contains("12 files"));
192 }
193
194 #[test]
195 fn signature_changed_event() {
196 let engine = test_engine();
197
198 let mut ctx = Context::new();
199 ctx.insert("entity_type", Value::String("method".into()));
200 ctx.insert("name", Value::String("getUser".into()));
201 ctx.insert("consumer_count", Value::Number(5));
202 ctx.insert(
203 "consumers",
204 Value::List(vec![
205 "ProfileController".into(),
206 "AuthMiddleware".into(),
207 "UserTest".into(),
208 "AdminPanel".into(),
209 "SettingsPage".into(),
210 ]),
211 );
212 let mut session = Session::new();
213
214 let result = engine
215 .render(&mut session, "code.signature_changed", &ctx)
216 .unwrap();
217 assert!(result.contains("getUser"));
218 assert!(result.contains("method"));
219 assert!(result.contains("5 callers"));
220 }
221
222 #[test]
223 fn variation_produces_different_templates() {
224 let mut engine1 = Engine::new(English::new())
225 .strictness(Strictness::Strict)
226 .variation(Variation::Fixed);
227 register(&mut engine1).unwrap();
228
229 let mut engine2 = Engine::new(English::new())
230 .strictness(Strictness::Strict)
231 .variation(Variation::Seeded(999));
232 register(&mut engine2).unwrap();
233
234 let mut ctx = Context::new();
235 ctx.insert("entity_type", Value::String("class".into()));
236 ctx.insert("old_name", Value::String("Foo".into()));
237 ctx.insert("new_name", Value::String("Bar".into()));
238 ctx.insert("consumer_count", Value::Number(2));
239 ctx.insert("consumers", Value::List(vec!["A".into(), "B".into()]));
240
241 let mut session1 = Session::new();
242 let mut session2 = Session::new();
243 let result1 = engine1.render(&mut session1, "code.renamed", &ctx).unwrap();
244 let result2 = engine2.render(&mut session2, "code.renamed", &ctx).unwrap();
245
246 assert!(!result1.is_empty());
250 assert!(!result2.is_empty());
251 }
252
253 #[test]
254 fn low_salience_produces_terse_output() {
255 let engine = test_engine();
256 let mut ctx = Context::new();
257 ctx.insert("entity_type", Value::String("class".into()));
258 ctx.insert("name", Value::String("Trivial".into()));
259 ctx.insert("consumer_count", Value::Number(1));
260 ctx.insert("consumers", Value::List(vec!["OnlyConsumer".into()]));
261 let mut session = Session::new();
262
263 let result = engine.render(&mut session, "code.modified", &ctx).unwrap();
264 assert!(
266 !result.contains("affect"),
267 "Low salience should drop impact clause, got: {result}"
268 );
269 }
270
271 #[test]
272 fn high_salience_produces_elaborated_output() {
273 let engine = test_engine();
274 let mut ctx = Context::new();
275 ctx.insert("entity_type", Value::String("class".into()));
276 ctx.insert("name", Value::String("Critical".into()));
277 ctx.insert("consumer_count", Value::Number(50));
278 ctx.insert(
279 "consumers",
280 Value::List(vec![
281 "A".into(),
282 "B".into(),
283 "C".into(),
284 "D".into(),
285 "E".into(),
286 "F".into(),
287 "G".into(),
288 "H".into(),
289 ]),
290 );
291 let mut session = Session::new();
292
293 let result = engine.render(&mut session, "code.modified", &ctx).unwrap();
294 assert!(
296 result.contains("substantial")
297 || result.contains("significant")
298 || result.contains("recommended"),
299 "Expected elaborated high-salience output, got: {result}"
300 );
301 assert!(result.contains("50"));
303 }
304
305 #[test]
306 fn medium_salience_uses_default_templates() {
307 let engine = test_engine();
308 let mut ctx = Context::new();
309 ctx.insert("entity_type", Value::String("class".into()));
310 ctx.insert("name", Value::String("Standard".into()));
311 ctx.insert("consumer_count", Value::Number(5));
312 ctx.insert(
313 "consumers",
314 Value::List(vec![
315 "A".into(),
316 "B".into(),
317 "C".into(),
318 "D".into(),
319 "E".into(),
320 ]),
321 );
322 let mut session = Session::new();
323
324 let result = engine.render(&mut session, "code.modified", &ctx).unwrap();
325 assert!(result.contains("5"));
327 assert!(
328 result.contains("affect") || result.contains("review") || result.contains("dependent"),
329 "Expected medium-salience impact clause, got: {result}"
330 );
331 }
332}
333
334#[cfg(test)]
335mod tests_es {
336 use super::*;
337 use prosaic_core::{Context, Engine, Session, Strictness, Value, Variation};
338 use prosaic_grammar_es::Spanish;
339
340 fn test_engine() -> Engine {
341 let mut engine = Engine::new(Spanish::new())
342 .strictness(Strictness::Strict)
343 .variation(Variation::Fixed);
344 register_es(&mut engine).unwrap();
345 engine
346 }
347
348 #[test]
349 fn rename_event() {
350 let engine = test_engine();
351 let mut ctx = Context::new();
352 ctx.insert("entity_type", Value::String("clase".into()));
353 ctx.insert("old_name", Value::String("Foo".into()));
354 ctx.insert("new_name", Value::String("Foobar".into()));
355 ctx.insert("consumer_count", Value::Number(6));
356 ctx.insert(
357 "consumers",
358 Value::List(vec![
359 "Baz".into(),
360 "Qux".into(),
361 "Quux".into(),
362 "Corge".into(),
363 "Grault".into(),
364 "Garply".into(),
365 ]),
366 );
367 let mut session = Session::new();
368
369 let result = engine.render(&mut session, "code.renamed", &ctx).unwrap();
370 assert!(
371 result.contains("fue renombrado a")
372 || result.contains("ahora se llama")
373 || result.contains("ha sido renombrado a"),
374 "Expected Spanish rename phrase, got: {result}"
375 );
376 assert!(
377 result.contains("Foobar"),
378 "Expected new name, got: {result}"
379 );
380 }
381
382 #[test]
383 fn rename_event_includes_consumer_count() {
384 let engine = test_engine();
385 let mut ctx = Context::new();
386 ctx.insert("entity_type", Value::String("clase".into()));
387 ctx.insert("old_name", Value::String("Foo".into()));
388 ctx.insert("new_name", Value::String("Foobar".into()));
389 ctx.insert("consumer_count", Value::Number(6));
390 ctx.insert(
391 "consumers",
392 Value::List(vec!["Baz".into(), "Qux".into(), "Quux".into()]),
393 );
394 let mut session = Session::new();
395
396 let result = engine.render(&mut session, "code.renamed", &ctx).unwrap();
397 assert!(
398 result.contains("6"),
399 "Expected consumer count in output, got: {result}"
400 );
401 }
402
403 #[test]
404 fn delete_event() {
405 let engine = test_engine();
406 let mut ctx = Context::new();
407 ctx.insert("entity_type", Value::String("interfaz".into()));
408 ctx.insert("name", Value::String("UserProfile".into()));
409 ctx.insert("consumer_count", Value::Number(3));
410 ctx.insert(
411 "consumers",
412 Value::List(vec![
413 "UserService".into(),
414 "ProfilePage".into(),
415 "AdminPanel".into(),
416 ]),
417 );
418 let mut session = Session::new();
419
420 let result = engine.render(&mut session, "code.deleted", &ctx).unwrap();
421 assert!(
422 result.contains("fue eliminado")
423 || result.contains("ha sido eliminado")
424 || result.contains("ya no existe"),
425 "Expected Spanish delete phrase, got: {result}"
426 );
427 assert!(
428 result.contains("UserProfile"),
429 "Expected entity name, got: {result}"
430 );
431 }
432
433 #[test]
434 fn add_event() {
435 let engine = test_engine();
436 let mut ctx = Context::new();
437 ctx.insert("entity_type", Value::String("servicio".into()));
438 ctx.insert("name", Value::String("AuthGuard".into()));
439 ctx.insert("location", Value::String("src/guards/auth.guard.ts".into()));
440 let mut session = Session::new();
441
442 let result = engine.render(&mut session, "code.added", &ctx).unwrap();
443 assert!(result.contains("AuthGuard"), "got: {result}");
444 assert!(result.contains("src/guards/auth.guard.ts"), "got: {result}");
445 assert!(
446 result.contains("servicio"),
447 "Expected entity type in Spanish, got: {result}"
448 );
449 }
450
451 #[test]
452 fn modify_event() {
453 let engine = test_engine();
454 let mut ctx = Context::new();
455 ctx.insert("entity_type", Value::String("método".into()));
456 ctx.insert("name", Value::String("processOrder".into()));
457 ctx.insert("consumer_count", Value::Number(4));
458 ctx.insert(
459 "consumers",
460 Value::List(vec![
461 "OrderPage".into(),
462 "CartService".into(),
463 "CheckoutFlow".into(),
464 "OrderHistory".into(),
465 ]),
466 );
467 let mut session = Session::new();
468
469 let result = engine.render(&mut session, "code.modified", &ctx).unwrap();
470 assert!(
471 result.contains("fue modificado")
472 || result.contains("ha sido actualizado")
473 || result.contains("afectan"),
474 "Expected Spanish modify phrase, got: {result}"
475 );
476 assert!(result.contains("processOrder"), "got: {result}");
477 }
478
479 #[test]
480 fn move_event() {
481 let engine = test_engine();
482 let mut ctx = Context::new();
483 ctx.insert("entity_type", Value::String("clase".into()));
484 ctx.insert("name", Value::String("Logger".into()));
485 ctx.insert("old_location", Value::String("src/utils/logger.ts".into()));
486 ctx.insert("new_location", Value::String("src/core/logger.ts".into()));
487 ctx.insert("consumer_count", Value::Number(12));
488 ctx.insert(
489 "consumers",
490 Value::List(vec![
491 "AppModule".into(),
492 "AuthService".into(),
493 "UserService".into(),
494 "OrderService".into(),
495 ]),
496 );
497 let mut session = Session::new();
498
499 let result = engine.render(&mut session, "code.moved", &ctx).unwrap();
500 assert!(result.contains("Logger"), "got: {result}");
501 assert!(
502 result.contains("src/utils/logger.ts") || result.contains("src/core/logger.ts"),
503 "Expected location in output, got: {result}"
504 );
505 assert!(
506 result.contains("12"),
507 "Expected consumer count, got: {result}"
508 );
509 }
510
511 #[test]
512 fn signature_changed_event() {
513 let engine = test_engine();
514 let mut ctx = Context::new();
515 ctx.insert("entity_type", Value::String("método".into()));
516 ctx.insert("name", Value::String("getUser".into()));
517 ctx.insert("consumer_count", Value::Number(5));
518 ctx.insert(
519 "consumers",
520 Value::List(vec![
521 "ProfileController".into(),
522 "AuthMiddleware".into(),
523 "UserTest".into(),
524 "AdminPanel".into(),
525 "SettingsPage".into(),
526 ]),
527 );
528 let mut session = Session::new();
529
530 let result = engine
531 .render(&mut session, "code.signature_changed", &ctx)
532 .unwrap();
533 assert!(result.contains("getUser"), "got: {result}");
534 assert!(
535 result.contains("firma") || result.contains("invocador"),
536 "Expected Spanish signature/caller terms, got: {result}"
537 );
538 }
539
540 #[test]
541 fn low_salience_terse_output() {
542 let engine = test_engine();
543 let mut ctx = Context::new();
544 ctx.insert("entity_type", Value::String("clase".into()));
545 ctx.insert("name", Value::String("Trivial".into()));
546 ctx.insert("consumer_count", Value::Number(1));
547 ctx.insert("consumers", Value::List(vec!["OnlyConsumer".into()]));
548 let mut session = Session::new();
549
550 let result = engine.render(&mut session, "code.modified", &ctx).unwrap();
551 assert!(
553 !result.contains("afectan"),
554 "Low salience should drop impact clause, got: {result}"
555 );
556 }
557}