package main import ( "context" "strings" "testing" "github.com/kami/maven/internal/router" ) // The list she read at the mic is not the list a browser is looking at // (Vikunja #45 step 3). The clarify store was keyed per reach in #466; the // dialogue session was still one slot for the box, so "второй" typed on the web // closed the second task she had recited out loud. func TestCandidatesDoNotCrossReaches(t *testing.T) { h, st, _ := newClarifyHandler(t) voiceCtx := withDialogueID(context.Background(), dialogueIDFor(sourceVoice, "")) webCtx := withDialogueID(context.Background(), dialogueIDFor(sourceText, "web")) ids := seedTasks(t, st, "купить хлеб", "позвонить маме") putCandidates(h, voiceCtx, ids, "купить хлеб", "позвонить маме") if reply, handled := h.resolveCandidate(webCtx, "первую сделал", sourceText); handled { t.Fatalf("a web turn picked from the list she read aloud: %q", reply) } live, err := st.ListTasks(context.Background(), "live") if err != nil { t.Fatalf("list tasks: %v", err) } if len(live) != 2 { t.Fatalf("%d tasks live, want 2 — the web turn moved one", len(live)) } // The reach that was offered the list still owns it. if _, handled := h.resolveCandidate(voiceCtx, "первую сделал", sourceVoice); !handled { t.Fatal("the mic lost its own list") } } // A selection writes a fact, so the fact must name the reach the words arrived // on. It said "tap:voice" for a typed turn. func TestCandidateProvenanceFollowsTheReach(t *testing.T) { h, st, _ := newClarifyHandler(t) ctx := withDialogueID(context.Background(), dialogueIDFor(sourceText, "web")) ids := seedTasks(t, st, "купить хлеб") putCandidates(h, ctx, ids, "купить хлеб") if _, handled := h.resolveCandidate(ctx, "первую сделал", sourceText); !handled { t.Fatal("the pick was not acted on") } done, err := st.ListTasks(context.Background(), "done") if err != nil { t.Fatalf("list tasks: %v", err) } if len(done) != 1 { t.Fatalf("%d tasks done, want 1", len(done)) } if by := done[0].ResolvedBy; by != string(sourceText) { t.Errorf("resolved_by = %q, want %q", by, sourceText) } } // Anaphora is per reach too: an ellipsis typed on the web must not continue the // question he asked at the mic. Both surfaces stay usable at once, which is the // case a single-owner box actually hits — a phone open while he talks. func TestAnaphoraDoesNotCrossReaches(t *testing.T) { h, _, _ := newClarifyHandler(t) voiceCtx := withDialogueID(context.Background(), dialogueIDFor(sourceVoice, "")) webCtx := withDialogueID(context.Background(), dialogueIDFor(sourceText, "web")) now := h.now() h.rememberTurn(voiceCtx, nil, router.Decision{ Intent: router.IntentQuery, Utterance: "во сколько у меня встреча", }, now) if sess := h.dialogueSessions.Get(dialogueIDOf(webCtx), now); sess != nil { t.Fatalf("the web reach inherited the mic's turn: %+v", sess) } sess := h.dialogueSessions.Get(dialogueIDOf(voiceCtx), now) if sess == nil || !strings.Contains(sess.Slots.Text, "встреча") { t.Fatalf("the mic lost its own turn: %+v", sess) } }