Refactor widget tests for H5 line generation and validation
- Simplified tests for H5 line creation, focusing on single line generation with a numeric 16-digit label. - Updated assertions to validate the generated H5 line's label and URI structure. - Removed redundant tests related to default H5 line URLs and local access, consolidating functionality into fewer tests. - Added new tests to ensure correct URL generation from configured fallback order and query parameter handling. - Enhanced test coverage for restoring cached H5 line URLs and validating their structure.
This commit is contained in:
14
README.md
14
README.md
@@ -1,17 +1,11 @@
|
|||||||
# im_webview_app
|
# im_webview_app
|
||||||
|
|
||||||
Flutter WebView 套壳 App,启动后会请求 `/client_config/query` 获取 H5 线路配置,并加载第一条可用线路。
|
Flutter WebView 套壳 App。启动时只会生成并加载一个 H5 URL;如果当前 URL 探测或加载失败,壳层会重新生成下一个 URL 并替换当前 WebView,项目运行过程中始终只持有一个 H5 URL。
|
||||||
|
|
||||||
默认情况下,非本地 Web 访问会使用当前服务地址的 origin 作为 H5 线路和接口域名,例如访问 `https://app.example.com/app/` 时会请求 `https://app.example.com/client_config/query`。本地访问(如 `localhost`、`127.0.0.1`)继续使用固定兜底域名 `https://h5-test.imharry.work/`。
|
默认 URL 来自代码中的泛域名模板,会生成 16 位数字子域名,并通过查询参数把 Flutter 壳请求地址传给 H5:
|
||||||
|
|
||||||
## H5 线路切换
|
```text
|
||||||
|
https://1234567890123456.albzyuxq.vip/?flutter_shell=1&flutter_shell_url=https%3A%2F%2F1234567890123456.albzyuxq.vip
|
||||||
线路切换在 Flutter 套壳层完成。远程配置不可用时,会先使用启动兜底线路;也可以在打包时覆盖启动线路或配置接口地址:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
flutter build apk --release --dart-define=H5_LINE_URLS=https://h5-one.example/,https://h5-two.example/
|
|
||||||
flutter build apk --release --dart-define=CLIENT_CONFIG_QUERY_URL=https://api.example.com/client_config/query
|
|
||||||
flutter build apk --release --dart-define=BOOTSTRAP_H5_LINE_URL=https://h5-one.example/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 本地打包
|
## 本地打包
|
||||||
|
|||||||
@@ -12,175 +12,52 @@ class H5Line {
|
|||||||
Uri get uri => Uri.parse(url);
|
Uri get uri => Uri.parse(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Environment {
|
|
||||||
development,
|
|
||||||
staging,
|
|
||||||
production,
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppConfig {
|
class AppConfig {
|
||||||
static const String appName = '本地打包';
|
static const String appName = '本地打包';
|
||||||
static const String appLogo = '';
|
static const String appLogo = '';
|
||||||
static const String clientConfigDevice = 'h5';
|
|
||||||
static const String clientConfigSerialNo = 'h5-domain';
|
|
||||||
static const String _dartDefinedH5LineUrls =
|
|
||||||
String.fromEnvironment('H5_LINE_URLS');
|
|
||||||
static const String _dartDefinedClientConfigQueryUrl =
|
|
||||||
String.fromEnvironment('CLIENT_CONFIG_QUERY_URL');
|
|
||||||
// Bootstrap only: used before /client_config/query returns dynamic H5 domains.
|
|
||||||
static const String _bootstrapH5LineUrl = String.fromEnvironment(
|
|
||||||
'BOOTSTRAP_H5_LINE_URL',
|
|
||||||
defaultValue: 'https://h5-test.imharry.work/');
|
|
||||||
static const Environment currentEnvironment = Environment.production;
|
|
||||||
// Compatibility for the packaging service's legacy domain rewrite step.
|
|
||||||
static final Map<Environment, List<String>> _environmentHosts = {
|
|
||||||
Environment.production: [
|
|
||||||
_bootstrapH5LineUrl,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
static final Random _wildcardRandom = Random.secure();
|
|
||||||
|
|
||||||
static List<H5Line> get h5Lines {
|
static const String flutterShellMarkerQueryKey = 'flutter_shell';
|
||||||
final configuredUrls = _dartDefinedH5LineUrls.trim().isEmpty
|
static const String flutterShellUrlQueryKey = 'flutter_shell_url';
|
||||||
? _normalizedUniqueUrls(defaultH5LineUrlsForBaseUri(Uri.base))
|
static const List<String> wildcardH5DomainTemplates = [
|
||||||
: _normalizedUniqueUrls(_dartDefinedH5LineUrls.split(','));
|
'*.albzyuxq.vip',
|
||||||
final urls = configuredUrls.isEmpty
|
'*.zdyswvnt.vip',
|
||||||
? _normalizedUniqueUrls(const [_bootstrapH5LineUrl])
|
'*.gxifpxcr.vip',
|
||||||
: configuredUrls;
|
];
|
||||||
|
|
||||||
return [
|
static final Random _random = Random.secure();
|
||||||
for (var index = 0; index < urls.length; index += 1)
|
|
||||||
H5Line(label: '线路${index + 1}', url: urls[index]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, String> get clientConfigQueryPayload => const {
|
static H5Line get h5Line => createRandomH5Line();
|
||||||
'device': clientConfigDevice,
|
|
||||||
'serialNo': clientConfigSerialNo,
|
|
||||||
};
|
|
||||||
|
|
||||||
static List<Uri> get clientConfigQueryUris {
|
static H5Line createRandomH5Line({
|
||||||
final dartDefinedUrl = _dartDefinedClientConfigQueryUrl.trim();
|
int attempt = 0,
|
||||||
if (dartDefinedUrl.isNotEmpty) {
|
|
||||||
return [Uri.parse(dartDefinedUrl)];
|
|
||||||
}
|
|
||||||
|
|
||||||
final lines = h5Lines;
|
|
||||||
if (lines.isEmpty) {
|
|
||||||
return const [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientConfigQueryUrisForHomeUri(lines.first.uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<String> defaultH5LineUrlsForBaseUri(Uri baseUri) {
|
|
||||||
final runtimeHomeUrl = _runtimeHomeUrlFromBaseUri(baseUri);
|
|
||||||
if (runtimeHomeUrl != null) {
|
|
||||||
return [runtimeHomeUrl];
|
|
||||||
}
|
|
||||||
|
|
||||||
final environmentHosts = _environmentHosts[currentEnvironment];
|
|
||||||
if (environmentHosts == null || environmentHosts.isEmpty) {
|
|
||||||
return const [_bootstrapH5LineUrl];
|
|
||||||
}
|
|
||||||
return environmentHosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<Uri> clientConfigQueryUrisForHomeUri(Uri homeUri) {
|
|
||||||
return [
|
|
||||||
_originUriWithPath(homeUri, '/client_config/query'),
|
|
||||||
_originUriWithPath(homeUri, '/api/user/client_config/query'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<H5Line> h5LinesFromResourceUrl(
|
|
||||||
String resourceUrl, {
|
|
||||||
String Function()? wildcardFactory,
|
String Function()? wildcardFactory,
|
||||||
}) {
|
}) {
|
||||||
final urls = _normalizedUniqueUrls(
|
final template = wildcardH5DomainTemplates[
|
||||||
splitResourceUrlLines(resourceUrl).map(
|
attempt.abs() % wildcardH5DomainTemplates.length];
|
||||||
(value) => _replaceWildcardHost(
|
return h5LineFromUrl(
|
||||||
value,
|
_buildFlutterShellHomeUrl(
|
||||||
wildcardFactory: wildcardFactory,
|
_replaceWildcardHost(template, wildcardFactory: wildcardFactory),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return [
|
|
||||||
for (var index = 0; index < urls.length; index += 1)
|
|
||||||
H5Line(label: '线路${index + 1}', url: urls[index]),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<String> splitResourceUrlLines(String resourceUrl) {
|
static H5Line h5LineFromUrl(String value) {
|
||||||
if (resourceUrl.trim().isEmpty) {
|
return H5Line(label: '线路1', url: _normalizeHomeUrl(value));
|
||||||
return const [];
|
|
||||||
}
|
|
||||||
|
|
||||||
final lines = <String>[];
|
|
||||||
final seen = <String>{};
|
|
||||||
for (final value
|
|
||||||
in resourceUrl.split(RegExp(r'\\r\\n|\\n|\\r|\\t|[\r\n\t]+'))) {
|
|
||||||
final line = value.trim();
|
|
||||||
if (line.isEmpty || seen.contains(line)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
lines.add(line);
|
|
||||||
seen.add(line);
|
|
||||||
}
|
|
||||||
return List.unmodifiable(lines);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isLoginPageUrl(String? url) {
|
static String originForUrl(String value) {
|
||||||
if (url == null || url.isEmpty) {
|
final uri = Uri.parse(value);
|
||||||
return false;
|
return '${uri.scheme}://${uri.authority}';
|
||||||
}
|
|
||||||
|
|
||||||
final uri = Uri.tryParse(url);
|
|
||||||
if (uri == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _isLoginPath(uri.path) || _isLoginPath(uri.fragment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _isLoginPath(String path) {
|
static String _buildFlutterShellHomeUrl(String host) {
|
||||||
final normalized = path.trim();
|
final normalized = _normalizeHomeUrl(host);
|
||||||
if (normalized.isEmpty) {
|
final uri = Uri.parse(normalized);
|
||||||
return false;
|
final origin = originForUrl(normalized);
|
||||||
}
|
final queryParameters = Map<String, String>.from(uri.queryParameters)
|
||||||
|
..[flutterShellMarkerQueryKey] = '1'
|
||||||
final pathOnly = normalized.split('?').first.split('#').first;
|
..[flutterShellUrlQueryKey] = origin;
|
||||||
final segments = pathOnly.split('/').where((segment) => segment.isNotEmpty);
|
return uri.replace(queryParameters: queryParameters).toString();
|
||||||
if (segments.isEmpty) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return segments.last == 'login';
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<String> _normalizedUniqueUrls(Iterable<String> values) {
|
|
||||||
final urls = <String>[];
|
|
||||||
final seen = <String>{};
|
|
||||||
|
|
||||||
for (final value in values) {
|
|
||||||
final normalized = _tryNormalizeHomeUrl(value);
|
|
||||||
if (normalized == null || seen.contains(normalized)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
urls.add(normalized);
|
|
||||||
seen.add(normalized);
|
|
||||||
}
|
|
||||||
|
|
||||||
return List.unmodifiable(urls);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String? _tryNormalizeHomeUrl(String value) {
|
|
||||||
try {
|
|
||||||
return _normalizeHomeUrl(value);
|
|
||||||
} catch (_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _normalizeHomeUrl(String host) {
|
static String _normalizeHomeUrl(String host) {
|
||||||
@@ -198,69 +75,6 @@ class AppConfig {
|
|||||||
return uri.replace(path: path).toString();
|
return uri.replace(path: path).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
static String? _runtimeHomeUrlFromBaseUri(Uri baseUri) {
|
|
||||||
if (_isLocalRuntimeUri(baseUri)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _originUriWithPath(baseUri, '/').toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Uri _originUriWithPath(Uri uri, String path) {
|
|
||||||
final normalizedPath = path.startsWith('/') ? path : '/$path';
|
|
||||||
return Uri.parse('${uri.scheme}://${uri.authority}$normalizedPath');
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool _isLocalRuntimeUri(Uri uri) {
|
|
||||||
if (uri.scheme != 'http' && uri.scheme != 'https') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final host = _normalizeHostForCompare(uri.host);
|
|
||||||
if (host.isEmpty) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (host == 'localhost' || host.endsWith('.localhost') || host == '::1') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ipv4Parts = _tryParseIpv4Address(host);
|
|
||||||
if (ipv4Parts == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipv4Parts[0] == 127 || ipv4Parts.every((part) => part == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String _normalizeHostForCompare(String host) {
|
|
||||||
final normalized = host.trim().toLowerCase();
|
|
||||||
if (normalized.endsWith('.')) {
|
|
||||||
return normalized.substring(0, normalized.length - 1);
|
|
||||||
}
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<int>? _tryParseIpv4Address(String host) {
|
|
||||||
final parts = host.split('.');
|
|
||||||
if (parts.length != 4) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final octets = <int>[];
|
|
||||||
for (final part in parts) {
|
|
||||||
if (part.isEmpty || !RegExp(r'^\d+$').hasMatch(part)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final value = int.tryParse(part);
|
|
||||||
if (value == null || value < 0 || value > 255) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
octets.add(value);
|
|
||||||
}
|
|
||||||
return octets;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String _replaceWildcardHost(
|
static String _replaceWildcardHost(
|
||||||
String value, {
|
String value, {
|
||||||
String Function()? wildcardFactory,
|
String Function()? wildcardFactory,
|
||||||
@@ -276,12 +90,10 @@ class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String _randomWildcardLabel() {
|
static String _randomWildcardLabel() {
|
||||||
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
final digits = StringBuffer(_random.nextInt(9) + 1);
|
||||||
return String.fromCharCodes(
|
for (var index = 1; index < 16; index += 1) {
|
||||||
List<int>.generate(
|
digits.write(_random.nextInt(10));
|
||||||
16,
|
}
|
||||||
(_) => chars.codeUnitAt(_wildcardRandom.nextInt(chars.length)),
|
return digits.toString();
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1035
lib/main.dart
1035
lib/main.dart
File diff suppressed because it is too large
Load Diff
@@ -11,125 +11,58 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('exposes bootstrap H5 URL as Flutter shell lines', () {
|
test('creates one wildcard H5 line with a numeric 16 digit label', () {
|
||||||
final lines = AppConfig.h5Lines;
|
final line = AppConfig.h5Line;
|
||||||
|
|
||||||
expect(lines, isNotEmpty);
|
expect(line.label, '线路1');
|
||||||
expect(lines.first.label, '线路1');
|
|
||||||
expect(Uri.parse(lines.first.url).scheme, 'https');
|
|
||||||
expect(Uri.parse(lines.first.url).path, '/');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('uses current non-local service origin as default H5 line', () {
|
|
||||||
final urls = AppConfig.defaultH5LineUrlsForBaseUri(
|
|
||||||
Uri.parse('https://app.example.com/shell/index.html?from=local#hash'),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(urls, ['https://app.example.com/']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('preserves current service port when building default H5 line', () {
|
|
||||||
final urls = AppConfig.defaultH5LineUrlsForBaseUri(
|
|
||||||
Uri.parse('https://chat.example.com:8443/app/'),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(urls, ['https://chat.example.com:8443/']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('keeps fixed bootstrap H5 line for local browser access', () {
|
|
||||||
final urls = AppConfig.defaultH5LineUrlsForBaseUri(
|
|
||||||
Uri.parse('http://localhost:3000/'),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(urls, ['https://h5-test.imharry.work/']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('keeps fixed bootstrap H5 line for loopback browser access', () {
|
|
||||||
final urls = AppConfig.defaultH5LineUrlsForBaseUri(
|
|
||||||
Uri.parse('http://127.0.0.1:3000/'),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(urls, ['https://h5-test.imharry.work/']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('keeps fixed bootstrap H5 line for native app access', () {
|
|
||||||
final urls = AppConfig.defaultH5LineUrlsForBaseUri(
|
|
||||||
Uri.parse('file:///android_asset/flutter_assets/'),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(urls, ['https://h5-test.imharry.work/']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('uses fixed client config query parameters', () {
|
|
||||||
expect(AppConfig.clientConfigQueryPayload, {
|
|
||||||
'device': 'h5',
|
|
||||||
'serialNo': 'h5-domain',
|
|
||||||
});
|
|
||||||
expect(
|
expect(
|
||||||
AppConfig.clientConfigQueryUris.first.path,
|
line.uri.host,
|
||||||
'/client_config/query',
|
matches(RegExp(r'^[1-9][0-9]{15}\.(albzyuxq|zdyswvnt|gxifpxcr)\.vip$')),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('builds client config query APIs from current service origin', () {
|
test('generates the next single URL from the configured fallback order', () {
|
||||||
final uris = AppConfig.clientConfigQueryUrisForHomeUri(
|
var index = 0;
|
||||||
Uri.parse('https://app.example.com/app/index.html?from=local#hash'),
|
const labels = [
|
||||||
);
|
'1234567890123456',
|
||||||
|
'2234567890123456',
|
||||||
|
'3234567890123456',
|
||||||
|
];
|
||||||
|
final first = AppConfig.createRandomH5Line(
|
||||||
|
attempt: 0, wildcardFactory: () => labels[index++]);
|
||||||
|
final second = AppConfig.createRandomH5Line(
|
||||||
|
attempt: 1, wildcardFactory: () => labels[index++]);
|
||||||
|
final third = AppConfig.createRandomH5Line(
|
||||||
|
attempt: 2, wildcardFactory: () => labels[index++]);
|
||||||
|
|
||||||
|
expect(first.uri.host, '1234567890123456.albzyuxq.vip');
|
||||||
|
expect(second.uri.host, '2234567890123456.zdyswvnt.vip');
|
||||||
|
expect(third.uri.host, '3234567890123456.gxifpxcr.vip');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('passes Flutter shell request URL to H5 through query parameters', () {
|
||||||
|
final line = AppConfig.createRandomH5Line(
|
||||||
|
wildcardFactory: () => '1234567890123456',
|
||||||
|
);
|
||||||
|
final uri = line.uri;
|
||||||
|
|
||||||
|
expect(uri.queryParameters[AppConfig.flutterShellMarkerQueryKey], '1');
|
||||||
expect(
|
expect(
|
||||||
uris.map((uri) => uri.toString()),
|
uri.queryParameters[AppConfig.flutterShellUrlQueryKey],
|
||||||
[
|
'https://1234567890123456.albzyuxq.vip',
|
||||||
'https://app.example.com/client_config/query',
|
);
|
||||||
'https://app.example.com/api/user/client_config/query',
|
expect(
|
||||||
],
|
AppConfig.originForUrl(line.url),
|
||||||
|
'https://1234567890123456.albzyuxq.vip',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('splits client config resourceUrl into indexed H5 lines', () {
|
test('restores a cached H5 line URL with the single line label', () {
|
||||||
final lines = AppConfig.h5LinesFromResourceUrl(
|
final line = AppConfig.h5LineFromUrl(
|
||||||
'line-one.example\nhttps://line-two.example/app\n\nline-three.example',
|
'https://1234567890123456.albzyuxq.vip/?flutter_shell=1',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(lines.map((line) => line.label), ['线路1', '线路2', '线路3']);
|
expect(line.label, '线路1');
|
||||||
expect(Uri.parse(lines[0].url).host, 'line-one.example');
|
expect(line.uri.host, '1234567890123456.albzyuxq.vip');
|
||||||
expect(Uri.parse(lines[1].url).host, 'line-two.example');
|
|
||||||
expect(Uri.parse(lines[1].url).path, '/app');
|
|
||||||
expect(Uri.parse(lines[2].url).host, 'line-three.example');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('splits literal backslash-n resourceUrl values', () {
|
|
||||||
final lines = AppConfig.h5LinesFromResourceUrl(
|
|
||||||
r'line-one.example\n*.line-two.example\nline-three.example',
|
|
||||||
wildcardFactory: () => 'abcdefghijklmnop',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(lines.map((line) => line.label), ['线路1', '线路2', '线路3']);
|
|
||||||
expect(Uri.parse(lines[0].url).host, 'line-one.example');
|
|
||||||
expect(Uri.parse(lines[1].url).host, 'abcdefghijklmnop.line-two.example');
|
|
||||||
expect(Uri.parse(lines[2].url).host, 'line-three.example');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('expands wildcard H5 domains with a 16 character label', () {
|
|
||||||
final lines = AppConfig.h5LinesFromResourceUrl(
|
|
||||||
'*.albzyuxq.vip',
|
|
||||||
wildcardFactory: () => 'abcdefghijklmnop',
|
|
||||||
);
|
|
||||||
|
|
||||||
final host = Uri.parse(lines.single.url).host;
|
|
||||||
expect(host, 'abcdefghijklmnop.albzyuxq.vip');
|
|
||||||
expect(host.split('.').first.length, 16);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('detects only H5 login routes for shell line switch display', () {
|
|
||||||
expect(AppConfig.isLoginPageUrl('https://line-one.example/login'), isTrue);
|
|
||||||
expect(
|
|
||||||
AppConfig.isLoginPageUrl('https://line-one.example/app/login'), isTrue);
|
|
||||||
expect(
|
|
||||||
AppConfig.isLoginPageUrl('https://line-one.example/#/login'), isTrue);
|
|
||||||
expect(AppConfig.isLoginPageUrl('https://line-one.example/'), isFalse);
|
|
||||||
expect(
|
|
||||||
AppConfig.isLoginPageUrl('https://line-one.example/contact'), isFalse);
|
|
||||||
expect(
|
|
||||||
AppConfig.isLoginPageUrl('https://line-one.example/getCode'), isFalse);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user