mirror of
https://github.com/block/buzz.git
synced 2026-10-31 22:41:12 +00:00
<!-- Draft PR body for #7385 · fix(mobile): bind same-name mentions to exact selected identities · https://github.com/block/buzz/pull/7385 --> 🤖 ## Summary If two people in a channel share a display name, mobile couldn't tell them apart in mentions: picking the second could overwrite the first's selection, a rendered mention linked whichever same-name person matched first, and a later rename or a shorter name could re-bind the text to the wrong recipient. This PR binds every mention to the exact selected identity: - Each same-name selection keeps its own recipient instead of overwriting by name; conflicting picks get a qualified label like `Name (key…)`. - The longest matching label wins, so a shorter or interior name can never claim part of a longer one and steal its identity. - Rendering resolves recipients by the signed identity key from the event's tags — never from message text alone — so qualified labels stay correct regardless of tag order and survive later renames; an untagged ambiguous label blocks shorter mentions instead of silently re-binding. Ports the landed Desktop exact-recipient behavior (see `docs/mention-editor.md`). ### Related issue - Fixes: N/A. No mobile issue; Desktop's landed exact-recipient fixes are the reference this ports. - Independent base (`main`). #7387 (child) persists these exact selections in saved drafts. - Landing note: branches in this series overlap in the composer — when rebasing, keep exact/durable mention bindings, the explicit invite/reference-only choice, the account/visit/revision fences, and authorization before membership preparation and publication; don't resolve conflicts by taking either side wholesale. - Draft — not requesting merge yet; the security advisory run for this range timed out without results (no verdict). ### Testing - Regressions cover same-name collisions, prefix/overlap, removal, tag order, and renames. - At `acd841354a692243f1cb4c04059baad42f1d2abb`: `just mobile-check` and full `just mobile-test` pass (2,082 tests). The earlier full `just ci` receipt linked below is reused only for unchanged non-mobile code/tooling, not claimed as a rerun at this head. - Previous-head evidence: `just mobile-check`, the full mobile test suite, and full local `just ci` all pass — receipts in the [exact-head evidence comment](https://github.com/block/buzz/pull/7385#issuecomment-5574635836). - Verification is widget-test level; no native device or simulator run is claimed. To see it: mention two teammates with the same display name — both stay distinct, the second shows a qualified label, and the rendered message keeps both correct even after either renames. ### Screenshots Flutter production-widget test renders — not native-device screenshots or acceptance captures. | Scenario | Before | After | |---|---|---| | Qualified mention chip for same-name recipients, at 200% text scale (deliberate stress fixture) |  |  | <details> <summary>Capture provenance</summary> Rendered by the Flutter widget engine in a `flutter test` run (production widgets, production theme; no device or simulator). Before: this PR's declared base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`. After: its head `39afd73b0adfde14164f4b10dbd089cb498312b6`. </details> Signed-off-by: Logan Johnson <loganj@squareup.com>
57 lines
1.7 KiB
Dart
57 lines
1.7 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:buzz/shared/mentions/mention_bindings.dart';
|
|
|
|
void main() {
|
|
final first = 'a' * 64;
|
|
final second = 'b' * 64;
|
|
test('qualification is case-insensitive and collision-safe', () {
|
|
final bindings = {'Scout': first, 'Scout ($second)': first};
|
|
expect(selectedMentionLabel('scout', first, bindings), 'scout');
|
|
expect(
|
|
selectedMentionLabel('Scout', second, bindings),
|
|
'Scout ($second) 2',
|
|
);
|
|
});
|
|
test('longest occurrences block shorter and interior recipients', () {
|
|
expect(
|
|
mentionOccurrences('@Scout ($second) 2', [
|
|
'Scout',
|
|
'Scout ($second)',
|
|
'Scout ($second) 2',
|
|
]).single.label,
|
|
'Scout ($second) 2',
|
|
);
|
|
expect(mentionOccurrences('@A @B', ['A @B', 'B']).single.label, 'A @B');
|
|
expect(mentionOccurrences('mail@Scout', ['Scout']), isEmpty);
|
|
expect(mentionOccurrences('@Scout ($second)', ['Scout']), isEmpty);
|
|
expect(
|
|
mentionOccurrences('@Scout ($second) 2', ['Scout ($second)']),
|
|
isEmpty,
|
|
);
|
|
});
|
|
// Historical plain denial and both tag orders run through channel/thread UI.
|
|
test('ordinary ambiguity and qualification require signed authority', () {
|
|
expect(
|
|
renderedMentionBindings('@Scout', {
|
|
first: 'Scout',
|
|
second: 'Scout',
|
|
})['scout'],
|
|
{first, second},
|
|
);
|
|
expect(
|
|
renderedMentionBindings('@Scout ($second)', {
|
|
first: 'Scout',
|
|
})['scout ($second)'],
|
|
isEmpty,
|
|
);
|
|
expect(
|
|
renderedMentionBindings(
|
|
'@Old ($second)',
|
|
{second: 'New'},
|
|
[second],
|
|
)['old ($second)'],
|
|
{second},
|
|
);
|
|
});
|
|
}
|