248 lines
7.2 KiB
Dart
248 lines
7.2 KiB
Dart
/*
|
|
* Copyright (c) 2024-2026 Fabian Freund.
|
|
*
|
|
* This file is part of WebLibre
|
|
* (see https://weblibre.eu).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:weblibre/features/bangs/domain/services/reverse_match.dart';
|
|
|
|
void main() {
|
|
group('BangUrlPattern - main URL placeholder', () {
|
|
test('query param: captures simple search query', () {
|
|
final p = BangUrlPattern.parse('https://www.google.com/search?q={{{s}}}');
|
|
expect(p, isNotNull);
|
|
expect(
|
|
p!.match(Uri.parse('https://www.google.com/search?q=hello+world')),
|
|
'hello world',
|
|
);
|
|
});
|
|
|
|
test('query param: tolerates extra tracking params', () {
|
|
final p = BangUrlPattern.parse('https://www.google.com/search?q={{{s}}}');
|
|
expect(
|
|
p!.match(
|
|
Uri.parse(
|
|
'https://www.google.com/search?q=cats&hl=de&utm_source=foo',
|
|
),
|
|
),
|
|
'cats',
|
|
);
|
|
});
|
|
|
|
test('query param: enforces other required constants', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://www.google.com/search?q={{{s}}}&tbm=isch',
|
|
);
|
|
expect(
|
|
p!.match(Uri.parse('https://www.google.com/search?q=cats&tbm=isch')),
|
|
'cats',
|
|
);
|
|
expect(
|
|
p.match(Uri.parse('https://www.google.com/search?q=cats')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('path segment: captures search term', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://en.wikipedia.org/wiki/{{{s}}}',
|
|
);
|
|
expect(
|
|
p!.match(Uri.parse('https://en.wikipedia.org/wiki/Flutter')),
|
|
'Flutter',
|
|
);
|
|
});
|
|
|
|
test('path segment: enforces non-placeholder segments', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://www.ebay.com/sch/{{{s}}}/m.html',
|
|
);
|
|
expect(
|
|
p!.match(Uri.parse('https://www.ebay.com/sch/toys/m.html')),
|
|
'toys',
|
|
);
|
|
expect(
|
|
p.match(Uri.parse('https://www.ebay.com/sch/toys/wrong.html')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('path segment: supports prefix/suffix in same segment', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://site.com/find/prefix-{{{s}}}-end',
|
|
);
|
|
expect(
|
|
p!.match(Uri.parse('https://site.com/find/prefix-Foo-end')),
|
|
'Foo',
|
|
);
|
|
expect(
|
|
p.match(Uri.parse('https://site.com/find/Foo-end')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('host mismatch rejects', () {
|
|
final p = BangUrlPattern.parse('https://google.com/search?q={{{s}}}');
|
|
expect(
|
|
p!.match(Uri.parse('https://bing.com/search?q=foo')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('path length mismatch rejects', () {
|
|
final p = BangUrlPattern.parse('https://x.com/a?q={{{s}}}');
|
|
expect(
|
|
p!.match(Uri.parse('https://x.com/a/b?q=foo')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('empty captured query is rejected', () {
|
|
final p = BangUrlPattern.parse('https://google.com/?q={{{s}}}');
|
|
expect(p!.match(Uri.parse('https://google.com/?q=')), isNull);
|
|
});
|
|
});
|
|
|
|
group('BangUrlPattern - host normalization', () {
|
|
test('template without www matches www results host (Bing)', () {
|
|
final p = BangUrlPattern.parse('https://bing.com/search?q={{{s}}}');
|
|
expect(
|
|
p!.match(
|
|
Uri.parse('https://www.bing.com/search?q=test&toWww=1&redig=ABC'),
|
|
),
|
|
'test',
|
|
);
|
|
});
|
|
|
|
test('www template matches mobile results host (YouTube)', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://www.youtube.com/results?search_query={{{s}}}',
|
|
);
|
|
expect(
|
|
p!.match(Uri.parse('https://m.youtube.com/results?search_query=cats')),
|
|
'cats',
|
|
);
|
|
});
|
|
|
|
test('template without www matches www results host (Startpage)', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://startpage.com/do/metasearch.pl?query={{{s}}}',
|
|
);
|
|
expect(
|
|
p!.match(
|
|
Uri.parse('https://www.startpage.com/do/metasearch.pl?query=foo'),
|
|
),
|
|
'foo',
|
|
);
|
|
});
|
|
|
|
test('non-generic subdomains still must match', () {
|
|
final p = BangUrlPattern.parse('https://cn.bing.com/dict/search?q={{{s}}}');
|
|
expect(
|
|
p!.match(Uri.parse('https://www.bing.com/dict/search?q=foo')),
|
|
isNull,
|
|
);
|
|
});
|
|
});
|
|
|
|
group('BangUrlPattern - fragment placeholder', () {
|
|
test('hash query: 4chan-style #s={{{s}}}', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://boards.4chan.org/g/catalog#s={{{s}}}',
|
|
);
|
|
expect(p, isNotNull);
|
|
expect(
|
|
p!.match(Uri.parse('https://boards.4chan.org/g/catalog#s=keyword')),
|
|
'keyword',
|
|
);
|
|
});
|
|
|
|
test('hash path: SPA router /#/foo/{{{s}}}', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://research.lensai.eu/#/s/search/{{{s}}}',
|
|
);
|
|
expect(p, isNotNull);
|
|
expect(
|
|
p!.match(
|
|
Uri.parse('https://research.lensai.eu/#/s/search/quantum'),
|
|
),
|
|
'quantum',
|
|
);
|
|
});
|
|
|
|
test('hash path: rejects when fragment path constants do not match', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://research.lensai.eu/#/s/search/{{{s}}}',
|
|
);
|
|
expect(
|
|
p!.match(
|
|
Uri.parse('https://research.lensai.eu/#/s/answer/quantum'),
|
|
),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('hash query: tolerates extra fragment params', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://site.example/#s={{{s}}}',
|
|
);
|
|
expect(
|
|
p!.match(Uri.parse('https://site.example/#s=foo&page=2')),
|
|
'foo',
|
|
);
|
|
});
|
|
|
|
test('fragment-required template rejects URLs without fragment', () {
|
|
final p = BangUrlPattern.parse(
|
|
'https://boards.4chan.org/g/catalog#s={{{s}}}',
|
|
);
|
|
expect(
|
|
p!.match(Uri.parse('https://boards.4chan.org/g/catalog')),
|
|
isNull,
|
|
);
|
|
});
|
|
});
|
|
|
|
group('BangUrlPattern - unsupported templates', () {
|
|
test('multi-placeholder returns null', () {
|
|
expect(
|
|
BangUrlPattern.parse('https://x.com/?a={{{s}}}&b={{{s}}}'),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('placeholder in host returns null', () {
|
|
expect(BangUrlPattern.parse('https://{{{s}}}.example.com/'), isNull);
|
|
});
|
|
|
|
test('scheme-less template returns null', () {
|
|
expect(BangUrlPattern.parse('//example.com/?q={{{s}}}'), isNull);
|
|
});
|
|
});
|
|
|
|
group('BangUrlPattern - tie-break helpers', () {
|
|
test('more required constants score higher', () {
|
|
final plain = BangUrlPattern.parse('https://x.com/?q={{{s}}}')!;
|
|
final imgs = BangUrlPattern.parse(
|
|
'https://x.com/?q={{{s}}}&tbm=isch',
|
|
)!;
|
|
expect(imgs.constraintCount, greaterThan(plain.constraintCount));
|
|
});
|
|
});
|
|
}
|