Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 53 additions & 10 deletions packages/flame_texturepacker/lib/src/texture_packer_atlas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ Future<TextureAtlasData> _fromAssets(
);
} on Exception catch (e, stack) {
Error.throwWithStackTrace(
Exception('Error loading $assetsPrefix$path from assets: $e'),
Exception('Error loading $path (prefix: $assetsPrefix) from assets: $e'),
stack,
);
}
Expand Down Expand Up @@ -240,12 +240,33 @@ Future<TextureAtlasData> _parse(
final regions = <Region>[];
var hasIndexes = false;

final fileContent = fromStorage
? await XFile(path).readAsString()
: await (assets ?? Flame.assets).readFile(
'${assetsPrefix!}/$path',
package: package,
);
final String fileContent;
if (fromStorage) {
fileContent = await XFile(path).readAsString();
} else {
final prefix = (assetsPrefix ?? '').trim();
var cleanPath = path.trim();

if (cleanPath.startsWith('/')) {
cleanPath = cleanPath.substring(1);
}

String fullPath;
if (prefix.isEmpty) {
fullPath = cleanPath;
} else {
if (prefix.endsWith('/')) {
fullPath = '$prefix$cleanPath';
} else {
fullPath = '$prefix/$cleanPath';
}
}

fileContent = await (assets ?? Flame.assets).readFile(
fullPath,
package: package,
);
}

final lines = LineSplitter.split(
fileContent,
Expand All @@ -261,6 +282,8 @@ Future<TextureAtlasData> _parse(
fromStorage,
images,
package,
assetsPrefix: assetsPrefix,
assets: assets,
);
pages.add(page);

Expand Down Expand Up @@ -329,8 +352,10 @@ Future<Page> _parsePage(
String path,
bool fromStorage,
Images images,
String? package,
) async {
String? package, {
String? assetsPrefix,
AssetsCache? assets,
}) async {
final page = Page();
page.textureFile = lineQueue.removeFirst();

Expand All @@ -345,7 +370,25 @@ Future<Page> _parsePage(
images.add(texturePath, image);
page.texture = images.fromCache(texturePath);
} else {
page.texture = await images.load(texturePath, package: package);
final prefix = (assetsPrefix ?? '').trim();
var cleanPath = texturePath.trim();

if (cleanPath.startsWith('/')) {
cleanPath = cleanPath.substring(1);
}

String fullTexturePath;
if (prefix.isEmpty) {
fullTexturePath = cleanPath;
} else {
if (prefix.endsWith('/')) {
fullTexturePath = '$prefix$cleanPath';
} else {
fullTexturePath = '$prefix/$cleanPath';
}
}

page.texture = await images.load(fullTexturePath, package: package);
}

_parsePageProperties(lineQueue, page);
Expand Down
128 changes: 128 additions & 0 deletions packages/flame_texturepacker/test/atlas_path_resolution_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import 'dart:ui' as ui;
import 'package:flame/cache.dart';
import 'package:flame_texturepacker/flame_texturepacker.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class _MockAssetBundle extends Mock implements AssetBundle {}

class _MockImages extends Mock implements Images {}

class FakeImage extends Mock implements ui.Image {}

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

group('TexturePackerAtlas Path Resolution', () {
late _MockAssetBundle bundle;
late _MockImages images;
late String atlasContent;

setUpAll(() {
registerFallbackValue(const Symbol('package'));
});

setUp(() {
bundle = _MockAssetBundle();
images = _MockImages();
atlasContent = '''
test.png
size: 64, 64
filter: Nearest, Nearest
repeat: none
sprite1
bounds: 0, 0, 32, 32
''';

// Mock loading the atlas file
when(
() => bundle.loadString(any(), cache: any(named: 'cache')),
).thenAnswer((_) async => atlasContent);

// Mock loading an image
when(
() => images.load(any(), package: any(named: 'package')),
).thenAnswer((_) async => FakeImage());
});

test('should resolve paths correctly with leading slashes', () async {
final assets = AssetsCache(bundle: bundle);

await TexturePackerAtlas.load(
'/path/to/atlas_name.atlas',
assets: assets,
images: images,
);

// Verify it tried to load 'images/path/to/atlas.atlas'
// The leading slash in /path/to/atlas.atlas should be removed.
verify(
() => bundle.loadString(
'assets/images/path/to/atlas_name.atlas',
cache: any(named: 'cache'),
),
).called(1);
});

test('should handle assetsPrefix WITH trailing slash', () async {
final assets = AssetsCache(bundle: bundle);

await TexturePackerAtlas.load(
'atlas_name.atlas',
assetsPrefix: 'custom/',
assets: assets,
images: images,
);

verify(
() => bundle.loadString(
'assets/custom/atlas_name.atlas',
cache: any(named: 'cache'),
),
).called(1);
});

test('should handle assetsPrefix WITHOUT trailing slash', () async {
final assets = AssetsCache(bundle: bundle);

await TexturePackerAtlas.load(
'atlas_name.atlas',
assetsPrefix: 'custom',
assets: assets,
images: images,
);

verify(
() => bundle.loadString(
'assets/custom/atlas_name.atlas',
cache: any(named: 'cache'),
),
).called(1);
});

test('should pass package parameter to AssetsCache and Images', () async {
final assets = AssetsCache(bundle: bundle);

await TexturePackerAtlas.load(
'atlas_name.atlas',
assets: assets,
images: images,
package: 'my_package',
);

// Verify bundle call includes the package-prefixed path
verify(
() => bundle.loadString(
'packages/my_package/assets/images/atlas_name.atlas',
cache: any(named: 'cache'),
),
).called(1);

// Verify images.load call also includes the package
verify(
() => images.load('images/test.png', package: 'my_package'),
).called(1);
});
});
}