forked from baron/baron-sso
모바일 로그인창 테스트 강화
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -10,45 +10,36 @@ class TomlAssetLoader extends AssetLoader {
|
||||
@override
|
||||
Future<Map<String, dynamic>> load(String path, Locale locale) async {
|
||||
final languageCode = locale.languageCode.toLowerCase();
|
||||
final source = switch (languageCode) {
|
||||
'ko' => koStrings,
|
||||
'en' => enStrings,
|
||||
_ => enStrings,
|
||||
return switch (languageCode) {
|
||||
'ko' => _normalizedKoStrings,
|
||||
'en' => _normalizedEnStrings,
|
||||
_ => _normalizedEnStrings,
|
||||
};
|
||||
return _expandFlatTranslations(source);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _expandFlatTranslations(Map<String, String> flatMap) {
|
||||
final nested = <String, dynamic>{};
|
||||
for (final entry in flatMap.entries) {
|
||||
final key = entry.key;
|
||||
if (key.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
final segments = key.split('.');
|
||||
Map<String, dynamic> cursor = nested;
|
||||
for (var index = 0; index < segments.length; index++) {
|
||||
final segment = segments[index];
|
||||
if (segment.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
final isLeaf = index == segments.length - 1;
|
||||
if (isLeaf) {
|
||||
cursor[segment] = _normalizeLocalizationValue(entry.value);
|
||||
continue;
|
||||
}
|
||||
final next = cursor.putIfAbsent(segment, () => <String, dynamic>{});
|
||||
if (next is Map<String, dynamic>) {
|
||||
cursor = next;
|
||||
continue;
|
||||
}
|
||||
final replacement = <String, dynamic>{};
|
||||
cursor[segment] = replacement;
|
||||
cursor = replacement;
|
||||
}
|
||||
}
|
||||
return nested;
|
||||
final Map<String, dynamic> _normalizedKoStrings = _normalizeFlatTranslations(
|
||||
koStrings,
|
||||
);
|
||||
final Map<String, dynamic> _normalizedEnStrings = _normalizeFlatTranslations(
|
||||
enStrings,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _normalizeFlatTranslations(Map<String, String> flatMap) =>
|
||||
Map.fromEntries(
|
||||
flatMap.entries
|
||||
.where((entry) => _isUserfrontTranslationKey(entry.key))
|
||||
.map(
|
||||
(entry) =>
|
||||
MapEntry(entry.key, _normalizeLocalizationValue(entry.value)),
|
||||
),
|
||||
);
|
||||
|
||||
bool _isUserfrontTranslationKey(String key) {
|
||||
return key.startsWith('domain.') ||
|
||||
key.startsWith('msg.userfront.') ||
|
||||
key.startsWith('ui.userfront.') ||
|
||||
key.startsWith('ui.common.');
|
||||
}
|
||||
|
||||
String _normalizeLocalizationValue(String value) {
|
||||
|
||||
@@ -1781,7 +1781,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.7,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -45,10 +45,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.4.1"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -320,18 +320,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -653,26 +653,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.26.3"
|
||||
version: "1.30.0"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
version: "0.7.10"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
||||
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.12"
|
||||
version: "0.6.16"
|
||||
toml:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -18,17 +18,6 @@ const buildDir = process.argv[2] ?? join(__dirname, '..', 'build', 'web');
|
||||
const bootstrapPath = join(buildDir, 'flutter_bootstrap.js');
|
||||
const indexPath = join(buildDir, 'index.html');
|
||||
const hashableEntrypoints = ['main.dart.js', 'main.dart.mjs', 'main.dart.wasm'];
|
||||
const loginFontFallbackPreloads = [
|
||||
'https://fonts.gstatic.com/s/roboto/v32/KFOmCnqEu92Fr1Me4GZLCzYlKw.woff2',
|
||||
'https://fonts.gstatic.com/s/notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.110.woff2',
|
||||
'https://fonts.gstatic.com/s/notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.113.woff2',
|
||||
'https://fonts.gstatic.com/s/notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.114.woff2',
|
||||
'https://fonts.gstatic.com/s/notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.115.woff2',
|
||||
'https://fonts.gstatic.com/s/notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.116.woff2',
|
||||
'https://fonts.gstatic.com/s/notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.117.woff2',
|
||||
'https://fonts.gstatic.com/s/notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.118.woff2',
|
||||
'https://fonts.gstatic.com/s/notosanskr/v36/PbyxFmXiEBPT4ITbgNA5Cgms3VYcOA-vvnIzzuoyeLGC5nwuDo-KBTUm6CryotyJROlrnQ.119.woff2',
|
||||
];
|
||||
const compressibleExtensions = new Set([
|
||||
'.css',
|
||||
'.html',
|
||||
@@ -89,18 +78,15 @@ const canvasKitConfig = 'config:{canvasKitBaseUrl:"canvaskit/"}';
|
||||
|
||||
bootstrap = bootstrap.replace(
|
||||
/_flutter\.loader\.load\(\{\s*serviceWorkerSettings:\s*(\{[^{}]*\})\s*,\s*config:\s*\{[\s\S]*?serviceWorkerUrl[\s\S]*?\}\s*,\s*config:\s*\{[^}]*\}\s*\}\);/g,
|
||||
(_match, settings) =>
|
||||
`_flutter.loader.load({serviceWorkerSettings:${ensureServiceWorkerUrl(settings)},${canvasKitConfig}});`,
|
||||
`_flutter.loader.load({${canvasKitConfig}});`,
|
||||
);
|
||||
bootstrap = bootstrap.replace(
|
||||
/_flutter\.loader\.load\(\{\s*serviceWorkerSettings:\s*(\{[^{}]*\})\s*,\s*config:\s*\{[^}]*\}\s*\}\);/g,
|
||||
(_match, settings) =>
|
||||
`_flutter.loader.load({serviceWorkerSettings:${ensureServiceWorkerUrl(settings)},${canvasKitConfig}});`,
|
||||
`_flutter.loader.load({${canvasKitConfig}});`,
|
||||
);
|
||||
bootstrap = bootstrap.replace(
|
||||
/_flutter\.loader\.load\(\{\s*serviceWorkerSettings:\s*(\{[^{}]*\})\s*\}\);/g,
|
||||
(_match, settings) =>
|
||||
`_flutter.loader.load({serviceWorkerSettings:${ensureServiceWorkerUrl(settings)},${canvasKitConfig}});`,
|
||||
`_flutter.loader.load({${canvasKitConfig}});`,
|
||||
);
|
||||
bootstrap = bootstrap.replace(
|
||||
/_flutter\.loader\.load\(\);/g,
|
||||
@@ -116,6 +102,9 @@ if (existsSync(indexPath)) {
|
||||
let index = readFileSync(indexPath, 'utf8');
|
||||
const preloadLinks = [
|
||||
'<link rel="preload" href="flutter_bootstrap.js" as="script" />',
|
||||
hashedEntrypoints.has('main.dart.js')
|
||||
? `<link rel="preload" href="${hashedEntrypoints.get('main.dart.js')}" as="script" />`
|
||||
: '',
|
||||
hashedEntrypoints.has('main.dart.mjs')
|
||||
? `<link rel="modulepreload" href="${hashedEntrypoints.get('main.dart.mjs')}" />`
|
||||
: '',
|
||||
@@ -124,22 +113,22 @@ if (existsSync(indexPath)) {
|
||||
: '',
|
||||
'<link rel="modulepreload" href="canvaskit/skwasm.js" />',
|
||||
'<link rel="preload" href="canvaskit/skwasm.wasm" as="fetch" type="application/wasm" crossorigin />',
|
||||
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />',
|
||||
...loginFontFallbackPreloads.map(
|
||||
(href) =>
|
||||
`<link rel="preload" href="${href}" as="fetch" type="font/woff2" crossorigin />`,
|
||||
),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n ');
|
||||
|
||||
index = index
|
||||
.replace(/\n\s*<link rel="preload" href="flutter_bootstrap\.js" as="script" \/>/g, '')
|
||||
.replace(/\n\s*<link rel="preload" href="main\.dart\.[^"]+\.js" as="script" \/>/g, '')
|
||||
.replace(/\n\s*<link rel="modulepreload" href="main\.dart\.[^"]+\.mjs" \/>/g, '')
|
||||
.replace(
|
||||
/\n\s*<link rel="preload" href="main\.dart\.[^"]+\.wasm" as="fetch" type="application\/wasm" crossorigin \/>/g,
|
||||
'',
|
||||
)
|
||||
.replace(
|
||||
/\n\s*<link rel="preload" href="assets\/assets\/translations\/(?:en|ko)\.toml" as="fetch" crossorigin \/>/g,
|
||||
'',
|
||||
)
|
||||
.replace(/\n\s*<link rel="modulepreload" href="canvaskit\/skwasm\.js" \/>/g, '')
|
||||
.replace(
|
||||
/\n\s*<link rel="preload" href="canvaskit\/skwasm\.wasm" as="fetch" type="application\/wasm" crossorigin \/>/g,
|
||||
@@ -313,34 +302,4 @@ async function cacheFirst(request) {
|
||||
`;
|
||||
}
|
||||
|
||||
function ensureServiceWorkerUrl(settings) {
|
||||
const serviceWorkerUrl = `"/flutter_service_worker.js?v=" + ${serviceWorkerVersionExpression(settings)}`;
|
||||
if (/serviceWorkerUrl\s*:/.test(settings)) {
|
||||
return settings.replace(
|
||||
/serviceWorkerUrl\s*:\s*[^,\n}]+,?/,
|
||||
`serviceWorkerUrl: ${serviceWorkerUrl},`,
|
||||
);
|
||||
}
|
||||
|
||||
const closingBraceIndex = settings.lastIndexOf('}');
|
||||
if (closingBraceIndex < 0) {
|
||||
return settings;
|
||||
}
|
||||
const beforeClosing = settings.slice(0, closingBraceIndex).trimEnd();
|
||||
const afterClosing = settings.slice(closingBraceIndex);
|
||||
const separator =
|
||||
beforeClosing.endsWith('{') || beforeClosing.endsWith(',') ? '' : ',';
|
||||
return `${beforeClosing}${separator}
|
||||
serviceWorkerUrl: ${serviceWorkerUrl},
|
||||
${afterClosing}`;
|
||||
}
|
||||
|
||||
function serviceWorkerVersionExpression(settings) {
|
||||
const match = settings.match(/serviceWorkerVersion\s*:\s*([^,\n}]+)/);
|
||||
return (
|
||||
match?.[1]?.replace(/\/\*[\s\S]*?\*\//g, '').trim() ??
|
||||
'serviceWorkerVersion'
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[userfront] optimized ${basename(buildDir)} with hashed entrypoints and brotli assets`);
|
||||
|
||||
50
userfront/test/toml_asset_loader_test.dart
Normal file
50
userfront/test/toml_asset_loader_test.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:userfront/core/i18n/toml_asset_loader.dart';
|
||||
import 'package:userfront/i18n_data.dart';
|
||||
|
||||
void main() {
|
||||
test('TomlAssetLoader keeps flat keys for fast startup lookup', () async {
|
||||
const loader = TomlAssetLoader();
|
||||
|
||||
final translations = await loader.load(
|
||||
'assets/translations',
|
||||
const Locale('en'),
|
||||
);
|
||||
|
||||
expect(translations['domain.company.baron'], 'Baron');
|
||||
expect(translations['domain'], isNull);
|
||||
});
|
||||
|
||||
test('English signup policy copy stays small enough for first render', () {
|
||||
const sensitiveKeys = [
|
||||
'msg.userfront.signup.privacy_full',
|
||||
'msg.userfront.signup.tos_full',
|
||||
];
|
||||
|
||||
for (final key in sensitiveKeys) {
|
||||
final value = enStrings[key];
|
||||
expect(value, isNotNull, reason: key);
|
||||
expect(value!.length, lessThan(1024), reason: key);
|
||||
expect(value.contains(r'\\\\'), isFalse, reason: key);
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'TomlAssetLoader excludes non-userfront dictionaries at startup',
|
||||
() async {
|
||||
const loader = TomlAssetLoader();
|
||||
|
||||
final translations = await loader.load(
|
||||
'assets/translations',
|
||||
const Locale('en'),
|
||||
);
|
||||
|
||||
expect(translations['ui.admin.nav.api_keys'], isNull);
|
||||
expect(translations['ui.dev.console_title'], isNull);
|
||||
expect(translations['ui.userfront.login.action.submit'], 'Sign in');
|
||||
expect(translations['ui.common.theme_light'], 'Light');
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -53,7 +53,7 @@
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
gap: 18px;
|
||||
inset: 0;
|
||||
justify-content: center;
|
||||
min-height: 100%;
|
||||
@@ -83,6 +83,20 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#baron-bootstrap-shell .signin-preview {
|
||||
align-items: center;
|
||||
background: #1d4ed8;
|
||||
border-radius: 8px;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
font-size: clamp(15px, 3vw, 18px);
|
||||
font-weight: 700;
|
||||
justify-content: center;
|
||||
min-height: 48px;
|
||||
min-width: min(280px, 80vw);
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
#baron-bootstrap-shell .loader {
|
||||
animation: baron-spin 880ms linear infinite;
|
||||
border: 5px solid #cbd5e1;
|
||||
@@ -104,13 +118,22 @@
|
||||
<h1>Baron SW Portal</h1>
|
||||
<div class="loader" aria-hidden="true"></div>
|
||||
<p>Loading sign-in</p>
|
||||
<div class="signin-preview" aria-hidden="true">Sign in</div>
|
||||
</main>
|
||||
<script>
|
||||
var baronBootstrapStartedAt = performance.now();
|
||||
var baronMinimumShellMs = 1100;
|
||||
window.addEventListener("flutter-first-frame", function () {
|
||||
document.body.classList.add("flutter-ready");
|
||||
window.setTimeout(function () {
|
||||
document.getElementById("baron-bootstrap-shell")?.remove();
|
||||
}, 220);
|
||||
var elapsedMs = performance.now() - baronBootstrapStartedAt;
|
||||
window.setTimeout(
|
||||
function () {
|
||||
document.body.classList.add("flutter-ready");
|
||||
window.setTimeout(function () {
|
||||
document.getElementById("baron-bootstrap-shell")?.remove();
|
||||
}, 220);
|
||||
},
|
||||
Math.max(0, baronMinimumShellMs - elapsedMs),
|
||||
);
|
||||
});
|
||||
</script>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
|
||||
Reference in New Issue
Block a user