You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
meshcore-client/test/connector/mention_contract_test.dart

124 lines
4.7 KiB

import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_open/connector/meshcore_connector.dart';
/// Cross-repo contract tests for the `@[name]` self-mention rule (client #486,
/// firmware #510). Firmware implements the identical rule, so any change that
/// breaks one of these breaks agreement with the device and must ship in an
/// aligned build pair.
void main() {
group('@[name] matching', () {
test('matches the canonical bracketed form', () {
expect(
MeshCoreConnector.mentionsName('hey @[Ben] you there', 'Ben'),
isTrue,
);
});
test('bare @name does not match', () {
expect(
MeshCoreConnector.mentionsName('hey @Ben you there', 'Ben'),
isFalse,
);
});
test('matches as a plain substring, not anchored or word-bounded', () {
// Deliberate: the rule is `contains`, and firmware must agree.
expect(MeshCoreConnector.mentionsName('x@[Ben]y', 'Ben'), isTrue);
});
test('the name is compared VERBATIM, whitespace included (#497)', () {
// Owner ruling 2026-08-01: @[name] is a wire token carrying the advert
// name byte-for-byte. A name with surrounding spaces is a DIFFERENT
// name, and trimming it here is what stopped mentions beeping.
expect(MeshCoreConnector.mentionsName('yo @[Ben]', ' Ben '), isFalse);
expect(
MeshCoreConnector.mentionsName('yo @[ Ben ]', ' Ben '),
isTrue,
);
expect(MeshCoreConnector.mentionsName('yo @[Ben ]', 'Ben '), isTrue);
expect(MeshCoreConnector.mentionsName('yo @[Ben]', 'Ben '), isFalse);
});
test('empty or whitespace-only self-name matches nothing', () {
expect(MeshCoreConnector.mentionsName('yo @[Ben]', ''), isFalse);
expect(MeshCoreConnector.mentionsName('yo @[Ben]', ' '), isFalse);
expect(MeshCoreConnector.mentionsName('yo @[Ben]', null), isFalse);
});
test('a name that is not mentioned does not match', () {
expect(MeshCoreConnector.mentionsName('yo @[Alice]', 'Ben'), isFalse);
expect(
MeshCoreConnector.mentionsName('no mentions here', 'Ben'),
isFalse,
);
});
});
group('ASCII-only case folding (owner decision 2026-07-31, #486)', () {
test('ASCII names match case-insensitively in both directions', () {
expect(MeshCoreConnector.mentionsName('yo @[BEN]', 'ben'), isTrue);
expect(MeshCoreConnector.mentionsName('yo @[ben]', 'BEN'), isTrue);
expect(MeshCoreConnector.mentionsName('yo @[BeN]', 'bEn'), isTrue);
});
test('non-ASCII names compare case-sensitively', () {
// The deliberate divergence from String.toLowerCase(): firmware folds
// bytes and cannot do Unicode case mapping, so the client must not
// either, or the two sides disagree on the same message.
expect(MeshCoreConnector.mentionsName('yo @[Érik]', 'Érik'), isTrue);
expect(MeshCoreConnector.mentionsName('yo @[érik]', 'Érik'), isFalse);
expect(MeshCoreConnector.mentionsName('yo @[ÉRIK]', 'érik'), isFalse);
});
test('ASCII folding still applies around non-ASCII characters', () {
// "Ben-Érik": the ASCII half folds, the accented character does not.
expect(
MeshCoreConnector.mentionsName('yo @[BEN-Érik]', 'ben-Érik'),
isTrue,
);
expect(
MeshCoreConnector.mentionsName('yo @[BEN-érik]', 'ben-Érik'),
isFalse,
);
});
test('non-letter ASCII is untouched by the fold', () {
expect(MeshCoreConnector.mentionsName('yo @[Node_7]', 'node_7'), isTrue);
expect(MeshCoreConnector.mentionsName('yo @[N0DE-7]', 'n0de-7'), isTrue);
});
test(
'names containing spaces work, which bare @name could not delimit',
() {
expect(
MeshCoreConnector.mentionsName('yo @[Base Station]', 'base station'),
isTrue,
);
},
);
});
group('foldAscii is the single equivalence rule (Gemini review, #497)', () {
test('ASCII case collapses, non-ASCII case does not', () {
// Dedup used String.toLowerCase(), which collapses these; matching uses
// foldAscii, which keeps them distinct. One of two real contacts would
// have silently vanished from the mention list while staying matchable.
expect('É'.toLowerCase() == 'é'.toLowerCase(), isTrue);
expect(
MeshCoreConnector.foldAscii('É') == MeshCoreConnector.foldAscii('é'),
isFalse,
);
expect(
MeshCoreConnector.foldAscii('Ben') ==
MeshCoreConnector.foldAscii('ben'),
isTrue,
);
});
test('folding leaves non-ASCII bytes untouched', () {
expect(MeshCoreConnector.foldAscii('Érik'), equals('Érik'));
expect(MeshCoreConnector.foldAscii('BEN'), equals('ben'));
});
});
}

Powered by TurnKey Linux.