diff --git a/src/Actions/FetchMainTheme.php b/src/Actions/FetchMainTheme.php new file mode 100644 index 00000000..9814ac21 --- /dev/null +++ b/src/Actions/FetchMainTheme.php @@ -0,0 +1,34 @@ +api()->graph('{ + themes(first: 1, roles: MAIN) { + nodes { + id + name + } + } + }'); + + if (blank(data_get($response['body']->toArray(), 'data.themes.userErrors'))) { + return data_get($response['body']->toArray(), 'data.themes.nodes.0', []); + } + + Log::error('Fetching main theme error: '.json_encode(data_get($response['body']->toArray(), 'data.themes.userErrors'))); + + return []; + } +} diff --git a/src/Actions/FetchThemeAssets.php b/src/Actions/FetchThemeAssets.php new file mode 100644 index 00000000..5d35d14d --- /dev/null +++ b/src/Actions/FetchThemeAssets.php @@ -0,0 +1,49 @@ + $filenames + */ + public function handle(ShopModel $shop, string $mainThemeId, array $filenames): array + { + $response = $shop->api()->graph('query ($id: ID!, $filenames: [String!]) { + theme(id: $id) { + id + name + role + files(filenames: $filenames) { + nodes { + filename + body { + ... on OnlineStoreThemeFileBodyText { + content + } + } + } + } + } + }', [ + 'id' => $mainThemeId, + 'filenames' => $filenames, + ]); + + if (blank(data_get($response['body']->toArray(), 'data.theme.userErrors'))) { + return array_map(fn (array $data) => [ + 'filename' => $data['filename'], + 'content' => $data['body']['content'] ?? '', + ], data_get($response['body']->toArray(), 'data.theme.files.nodes')); + } + + Log::error('Fetching settings data error: '.json_encode(data_get($response['body']->toArray(), 'data.theme.userErrors'))); + + return []; + } +} diff --git a/src/Actions/VerifyThemeSupport.php b/src/Actions/VerifyThemeSupport.php index d51b0ccf..0e4c64a7 100644 --- a/src/Actions/VerifyThemeSupport.php +++ b/src/Actions/VerifyThemeSupport.php @@ -1,45 +1,154 @@ cacheInterval = (string) Str::of(Util::getShopifyConfig('theme_support.cache_interval')) + ->plural() + ->ucfirst() + ->start('add'); + + $this->cacheDuration = (int) Util::getShopifyConfig('theme_support.cache_duration'); } public function __invoke(ShopId $shopId): int { - $this->themeHelper->extractStoreMainTheme($shopId); + $shop = $this->shopQuery->getById($shopId); - if ($this->themeHelper->themeIsReady()) { - $templateJSONFiles = $this->themeHelper->templateJSONFiles(); - $templateMainSections = $this->themeHelper->mainSections($templateJSONFiles); - $sectionsWithAppBlock = $this->themeHelper->sectionsWithAppBlock($templateMainSections); + /** @var array{id: string, name: string} */ + $mainTheme = Cache::remember( + "mainTheme.{$shop->getId()->toNative()}", + now()->{$this->cacheInterval}($this->cacheDuration), + fn () => $this->fetchMainTheme->handle($shop) + ); - $hasTemplates = count($templateJSONFiles) > 0; - $allTemplatesHasRightType = count($templateJSONFiles) === count($sectionsWithAppBlock); - $templatesСountWithRightType = count($sectionsWithAppBlock); + if (isset($mainTheme['id'])) { + /** @var array */ + $assets = Cache::remember( + "assets.{$mainTheme['id']}.{$shop->getId()->toNative()}", + now()->{$this->cacheInterval}($this->cacheDuration), + fn () => $this->fetchThemeAssets->handle( + shop: $shop, + mainThemeId: $mainTheme['id'], + filenames: self::ASSET_FILE_NAMES + ) + ); + $templateMainSections = $this->mainSections( + shop: $shop, + mainTheme: $mainTheme, + assets: $assets + ); + $sectionsWithAppBlock = $this->sectionsWithAppBlock($templateMainSections); - switch (true) { - case $hasTemplates && $allTemplatesHasRightType: - return ThemeSupportLevel::FULL; + $hasTemplates = count($assets) > 0; + $allTemplatesHasRightType = count($assets) === count($sectionsWithAppBlock); + $hasTemplatesCountWithRightType = count($sectionsWithAppBlock) > 0; - case $templatesСountWithRightType: - return ThemeSupportLevel::PARTIAL; - - default: - return ThemeSupportLevel::UNSUPPORTED; - } + return match (true) { + $hasTemplates && $allTemplatesHasRightType => ThemeSupportLevel::FULL, + $hasTemplatesCountWithRightType => ThemeSupportLevel::PARTIAL, + default => ThemeSupportLevel::UNSUPPORTED + }; } return ThemeSupportLevel::UNSUPPORTED; } + + /** + * @template T + * @template Z + * + * @param Z $mainTheme + * @param T $assets + * + * @return T + */ + private function mainSections(ShopModel $shop, array $mainTheme, array $assets): array + { + $filenamesForMainSections = array_filter( + array_map(function ($asset) { + $content = $asset['content']; + + if (! $this->json_validate($content)) { + $content = preg_replace("#(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|([\s\t]//.*)|(^//.*)#", '', $content); + } + + $assetContent = json_decode($content, true); + + + $mainAsset = array_filter($assetContent['sections'], function ($value, $key) { + return $key == self::MAIN_ROLE || str_starts_with($value['type'], self::MAIN_ROLE); + }, ARRAY_FILTER_USE_BOTH); + + if ($mainAsset) { + return 'sections/'.end($mainAsset)['type'].'.liquid'; + } + }, $assets) + ); + + return Cache::remember( + "mainSections.{$mainTheme['id']}.".sha1(implode('|', $filenamesForMainSections)), + now()->{$this->cacheInterval}($this->cacheDuration), + fn () => $this->fetchThemeAssets->handle( + shop: $shop, + mainThemeId: $mainTheme['id'], + filenames: [...$filenamesForMainSections] + ) + ); + } + + /** + * @template T + * + * @param T $templateMainSections + * + * @return T + */ + private function sectionsWithAppBlock(array $templateMainSections): array + { + return array_filter(array_map(function ($file) { + $acceptsAppBlock = false; + + preg_match('/\{\%-?\s+schema\s+-?\%\}([\s\S]*?)\{\%-?\s+endschema\s+-?\%\}/m', $file['content'], $matches); + $schema = json_decode($matches[1] ?? '{}', true); + + if ($schema && isset($schema['blocks'])) { + $acceptsAppBlock = in_array('@app', array_column($schema['blocks'], 'type')); + } + + return $acceptsAppBlock ? $file : null; + }, $templateMainSections)); + } + + + private function json_validate(string $string): bool + { + json_decode($string); + + return json_last_error() === JSON_ERROR_NONE; + } } diff --git a/src/Objects/Values/MainTheme.php b/src/Objects/Values/MainTheme.php deleted file mode 100644 index 6b467774..00000000 --- a/src/Objects/Values/MainTheme.php +++ /dev/null @@ -1,96 +0,0 @@ -id = $id; - $this->name = $name; - $this->role = $role; - } - - /** - * {@inheritDoc} - */ - public static function fromNative($native) - { - return new static( - NullableThemeId::fromNative(Arr::get($native, 'id')), - NullableThemeName::fromNative(Arr::get($native, 'name')), - NullableThemeRole::fromNative(Arr::get($native, 'role')) - ); - } - - /** - * Get theme id - * - * @return ThemeId - */ - public function getId() - { - return $this->id; - } - - /** - * Get theme name - * - * @return ThemeName - */ - public function getName() - { - return $this->name; - } - - /** - * Get theme role - * - * @return ThemeRole - */ - public function getRole() - { - return $this->role; - } -} diff --git a/src/Services/ThemeHelper.php b/src/Services/ThemeHelper.php deleted file mode 100644 index 0353cfaa..00000000 --- a/src/Services/ThemeHelper.php +++ /dev/null @@ -1,244 +0,0 @@ -shopQuery = $shopQuery; - - $this->cacheInterval = (string) Str::of(Util::getShopifyConfig('theme_support.cache_interval')) - ->plural() - ->ucfirst() - ->start('add'); - - $this->cacheDuration = Util::getShopifyConfig('theme_support.cache_duration'); - } - - /** - * Determine if theme is set and ready for testing. - * - * @return bool - */ - public function themeIsReady(): bool - { - return (bool) $this->mainTheme->getId()->toNative(); - } - - /** - * Extract store's main theme. - * - * @param ShopId $shopId The shop ID. - * - * @return void - */ - public function extractStoreMainTheme(ShopId $shopId): void - { - $this->shop = $this->shopQuery->getById($shopId); - - $themesResponse = $this->shop->api()->rest('GET', '/admin/themes.json'); - if (!$themesResponse['errors'] && isset($themesResponse['body']['themes'])) { - $themes = $themesResponse['body']['themes']->toArray(); - $key = array_search(self::MAIN_ROLE, array_column($themes, self::THEME_FIELD)); - - $this->mainTheme = MainTheme::fromNative($themes[$key]); - $this->extractThemeAssets(); - } else { - $this->mainTheme = MainTheme::fromNative([]); - } - } - - /** - * Extract main theme's assets. - * - * @return void - */ - private function extractThemeAssets(): void - { - $this->assets = Cache::remember( - "assets_{$this->mainTheme->getId()->toNative()}", - now()->{$this->cacheInterval}($this->cacheDuration), - function () { - return $this->shop->api()->rest( - 'GET', - "/admin/themes/{$this->mainTheme->getId()->toNative()}/assets.json" - )['body']['assets']->toArray(); - } - ); - } - - /** - * Check if JSON template files exist for - * the template specified in `APP_BLOCK_TEMPLATES`. - * - * @return array - */ - public function templateJSONFiles(): array - { - return array_filter($this->assets, function ($asset) { - $match = false; - $blockTemplates = Util::getShopifyConfig('theme_support.templates'); - - foreach ($blockTemplates as $template) { - if ($asset['key'] == "templates/$template.json") { - $match = true; - - break; - } - } - - return $match; - }); - } - - /** - * Retrieve the body of JSON templates and find what section is set as `main`. - * - * @param array $templateJSONFiles The template files to filter through. - * - * @return array - */ - public function mainSections(array $templateJSONFiles): array - { - return array_filter(array_map(function ($file) { - $assetResponse = $this->fetchAsset($file); - $asset = $assetResponse['body']['asset']->toArray(); - $assetValue = json_decode($asset['value'], true); - - $mainAsset = array_filter($assetValue['sections'], function ($value, $key) { - return $key == self::MAIN_ROLE || str_starts_with($value['type'], self::MAIN_ROLE); - }, ARRAY_FILTER_USE_BOTH); - - if ($mainAsset) { - return array_merge(...array_filter($this->assets, function ($asset) use ($mainAsset) { - return $asset['key'] === 'sections/'.end($mainAsset)['type'].'.liquid'; - })); - } - }, $templateJSONFiles)); - } - - /** - * Request the contents of each section then check - * if it has a schema that contains a block of type '@app'. - * - * @param array $templateMainSections The template's main sections to filter through. - * - * @return array - */ - public function sectionsWithAppBlock(array $templateMainSections): array - { - return array_filter(array_map(function ($file) { - $acceptsAppBlock = false; - - $assetResponse = $this->fetchAsset($file); - $asset = $assetResponse['body']['asset']->toArray(); - - preg_match('/\{\%\s+schema\s+\%\}([\s\S]*?)\{\%\s+endschema\s+\%\}/m', $asset['value'], $matches); - $schema = json_decode($matches[1], true); - - if ($schema && isset($schema['blocks'])) { - $acceptsAppBlock = in_array('@app', array_column($schema['blocks'], 'type')); - } - - return $acceptsAppBlock ? $file : null; - }, $templateMainSections)); - } - - /** - * Fetch a theme's asset. - * - * @param array $file The theme file information to fetch or retrieve from cache. - * - * @return array - */ - private function fetchAsset(array $file): array - { - return Cache::remember( - "asset_{$this->mainTheme->getId()->toNative()}_{$file['key']}", - now()->{$this->cacheInterval}($this->cacheDuration), - function () use ($file) { - return $this->shop->api()->rest( - 'GET', - "/admin/themes/{$this->mainTheme->getId()->toNative()}/assets", - [ - 'asset' => ['key' => $file['key']], - ] - ); - } - ); - } -} diff --git a/src/ShopifyAppProvider.php b/src/ShopifyAppProvider.php index 0102d220..d94a68a7 100644 --- a/src/ShopifyAppProvider.php +++ b/src/ShopifyAppProvider.php @@ -42,7 +42,6 @@ use Osiset\ShopifyApp\Messaging\Jobs\WebhookInstaller; use Osiset\ShopifyApp\Services\ApiHelper; use Osiset\ShopifyApp\Services\ChargeHelper; -use Osiset\ShopifyApp\Services\ThemeHelper; use Osiset\ShopifyApp\Storage\Commands\Charge as ChargeCommand; use Osiset\ShopifyApp\Storage\Commands\Shop as ShopCommand; use Osiset\ShopifyApp\Storage\Observers\Shop as ShopObserver; @@ -189,13 +188,6 @@ public function register() ); }); - $this->app->bind(VerifyThemeSupportAction::class, function ($app) { - return new VerifyThemeSupportAction( - $app->make(IShopQuery::class), - $app->make(ThemeHelper::class) - ); - }); - $this->app->bind(ActivateUsageChargeAction::class, function ($app) { return new ActivateUsageChargeAction( $app->make(ChargeHelper::class), diff --git a/src/resources/database/migrations/2022_06_09_104819_add_theme_support_level_to_users_table.php b/src/resources/database/migrations/2022_06_09_104819_add_theme_support_level_to_users_table.php index 48cdfe0c..a2096eef 100644 --- a/src/resources/database/migrations/2022_06_09_104819_add_theme_support_level_to_users_table.php +++ b/src/resources/database/migrations/2022_06_09_104819_add_theme_support_level_to_users_table.php @@ -15,7 +15,7 @@ class AddThemeSupportLevelToUsersTable extends Migration public function up() { Schema::table(Util::getShopsTable(), function (Blueprint $table) { - $table->integer('theme_support_level')->nullable(); + $table->integer('theme_support_level')->nullable()->after('plan_id'); }); } diff --git a/tests/Actions/VerifyThemeSupportTest.php b/tests/Actions/VerifyThemeSupportTest.php index 8d84d4c4..d8b7e889 100644 --- a/tests/Actions/VerifyThemeSupportTest.php +++ b/tests/Actions/VerifyThemeSupportTest.php @@ -3,27 +3,21 @@ namespace Osiset\ShopifyApp\Test\Actions; use Osiset\ShopifyApp\Actions\VerifyThemeSupport; -use Osiset\ShopifyApp\Contracts\Queries\Shop as IShopQuery; use Osiset\ShopifyApp\Objects\Enums\ThemeSupportLevel; -use Osiset\ShopifyApp\Objects\Values\ShopId; -use Osiset\ShopifyApp\Services\ThemeHelper; +use Osiset\ShopifyApp\Test\Stubs\Api as ApiStub; use Osiset\ShopifyApp\Test\TestCase; class VerifyThemeSupportTest extends TestCase { - public function setUp(): void - { - parent::setUp(); - } - public function testStoreWithUndefinedMainTheme(): void { + $this->fakeGraphqlApi(['empty_theme']); $shop = factory($this->model)->create(); $action = $this->app->make(VerifyThemeSupport::class); $result = call_user_func( $action, - ShopId::fromNative($shop->id) + $shop->getId() ); $this->assertNotNull($result); @@ -32,13 +26,13 @@ public function testStoreWithUndefinedMainTheme(): void public function testStoreWithFullExtensionSupport(): void { + $this->fakeGraphqlApi(['main_theme', 'theme_with_one_asset', 'theme_with_one_section']); $shop = factory($this->model)->create(); - $themeHelperStub = $this->createThemeHelperStub(ThemeSupportLevel::FULL); - $action = new VerifyThemeSupport($this->app->make(IShopQuery::class), $themeHelperStub); + $action = $this->app->make(VerifyThemeSupport::class); $result = call_user_func( $action, - ShopId::fromNative($shop->id) + $shop->getId() ); $this->assertNotNull($result); @@ -48,12 +42,12 @@ public function testStoreWithFullExtensionSupport(): void public function testStoreWithPartialExtensionSupport(): void { $shop = factory($this->model)->create(); - $themeHelperStub = $this->createThemeHelperStub(ThemeSupportLevel::PARTIAL); - $action = new VerifyThemeSupport($this->app->make(IShopQuery::class), $themeHelperStub); + $this->fakeGraphqlApi(['main_theme', 'theme_with_three_assets', 'theme_with_one_section']); + $action = $this->app->make(VerifyThemeSupport::class); $result = call_user_func( $action, - ShopId::fromNative($shop->id) + $shop->getId() ); $this->assertNotNull($result); @@ -63,12 +57,12 @@ public function testStoreWithPartialExtensionSupport(): void public function testStoreWithoutExtensionSupport(): void { $shop = factory($this->model)->create(); - $themeHelperStub = $this->createThemeHelperStub(ThemeSupportLevel::UNSUPPORTED); - $action = new VerifyThemeSupport($this->app->make(IShopQuery::class), $themeHelperStub); + $this->fakeGraphqlApi(['main_theme', 'theme_with_empty_assets', 'theme_with_empty_sections']); + $action = $this->app->make(VerifyThemeSupport::class); $result = call_user_func( $action, - ShopId::fromNative($shop->id) + $shop->getId() ); $this->assertNotNull($result); @@ -77,30 +71,11 @@ public function testStoreWithoutExtensionSupport(): void /** * Create ThemeHelper stub - * - * @param int $level - * - * @return ThemeHelper */ - protected function createThemeHelperStub(int $level): ThemeHelper + protected function fakeGraphqlApi(array $responses): void { - $themeHelperStub = $this->createStub(ThemeHelper::class); - - $defaultThemeResponse = []; - - if ($level === ThemeSupportLevel::FULL) { - $defaultThemeResponse = [0, 1, 2, 3]; - } - - $themeHelperStub->method('themeIsReady')->willReturn(true); - $themeHelperStub->method('templateJSONFiles')->willReturn($defaultThemeResponse); - $themeHelperStub->method('mainSections')->willReturn($defaultThemeResponse); - $themeHelperStub->method('sectionsWithAppBlock')->willReturn( - $level === ThemeSupportLevel::PARTIAL - ? array_merge($defaultThemeResponse, [random_int(1, 99)]) - : $defaultThemeResponse - ); + $this->setApiStub(); - return $themeHelperStub; + ApiStub::stubResponses($responses); } } diff --git a/tests/Http/Middleware/VerifyShopifyTest.php b/tests/Http/Middleware/VerifyShopifyTest.php index 6f45ebb0..1b1df313 100644 --- a/tests/Http/Middleware/VerifyShopifyTest.php +++ b/tests/Http/Middleware/VerifyShopifyTest.php @@ -164,6 +164,7 @@ public function testTokenProcessingAndNotInstalledShop(): void [ 'token' => $this->buildToken(), 'shop' => 'non-existent.myshopify.com', + 'host' => 'loremipsum', ], // Request Params null, diff --git a/tests/Objects/Values/MainThemeTest.php b/tests/Objects/Values/MainThemeTest.php deleted file mode 100644 index 13fb10e9..00000000 --- a/tests/Objects/Values/MainThemeTest.php +++ /dev/null @@ -1,58 +0,0 @@ -mainTheme = MainTheme::fromNative([ - 'id' => self::THEME_ID, - 'name' => self::THEME_NAME, - 'role' => self::THEME_ROLE, - ]); - } - - public function testGetMainThemeId() - { - $themeId = $this->mainTheme->getId(); - - $this->assertInstanceOf(NullableThemeId::class, $themeId); - $this->assertEquals($themeId->toNative(), self::THEME_ID); - } - - public function testGetMainThemeName() - { - $themeName = $this->mainTheme->getName(); - - $this->assertInstanceOf(NullableThemeName::class, $themeName); - $this->assertEquals($themeName->toNative(), self::THEME_NAME); - } - - public function testGetMainThemeRole() - { - $themeRole = $this->mainTheme->getRole(); - - $this->assertInstanceOf(NullableThemeRole::class, $themeRole); - $this->assertEquals($themeRole->toNative(), self::THEME_ROLE); - } -} diff --git a/tests/Services/ThemeHelperTest.php b/tests/Services/ThemeHelperTest.php deleted file mode 100644 index 55e1e045..00000000 --- a/tests/Services/ThemeHelperTest.php +++ /dev/null @@ -1,130 +0,0 @@ -helper = $this->app->make(ThemeHelper::class); - $this->shop = factory($this->model)->create(); - } - - public function testThemeIsReady(): void - { - // Response stubbing - $this->setApiStub(); - ApiStub::stubResponses(['get_themes', 'get_theme_assets']); - - // Run extraction - $this->helper->extractStoreMainTheme($this->shop->getId()); - - $this->assertTrue($this->helper->themeIsReady()); - } - - public function testThemeIsReadyFailure(): void - { - // Response stubbing - $this->setApiStub(); - ApiStub::stubResponses(['empty_with_error']); - - // Run extraction - $this->helper->extractStoreMainTheme($this->shop->getId()); - - $this->assertFalse($this->helper->themeIsReady()); - } - - public function testTemplateJsonFiles(): void - { - // Response stubbing - $this->setApiStub(); - ApiStub::stubResponses(['get_themes', 'get_theme_assets']); - - // Run extraction - $this->helper->extractStoreMainTheme($this->shop->getId()); - - // No config defined, so the match should be empty - $this->assertEmpty($this->helper->templateJSONFiles()); - - // Define config, retest - $this->app['config']->set( - 'shopify-app.theme_support.templates', - [ - 'shop', // Matches fixture data for `templates/shop.json` - ] - ); - $this->assertNotEmpty($this->helper->templateJSONFiles()); - } - - public function testMainSections(): void - { - // Response stubbing - $this->setApiStub(); - ApiStub::stubResponses(['get_themes', 'get_theme_assets', 'get_theme_asset_template_json']); - - // Define config - $this->app['config']->set( - 'shopify-app.theme_support.templates', - [ - 'shop', // Matches fixture data for `templates/shop.json` - ] - ); - - // Run extraction - $this->helper->extractStoreMainTheme($this->shop->getId()); - - // Get the main sections... - // `templates/shop.json`'s content in `get_theme_asset_template_json` fixture - // has a main of `product` which converts to `sections/product.liquid` which also - // exists in `get_theme_assets` fixture - $jsonFiles = $this->helper->templateJSONFiles(); - $mainSections = $this->helper->mainSections($jsonFiles); - - $this->assertNotEmpty($mainSections); - } - - public function testSectionsWithAppBlock(): void - { - // Response stubbing - $this->setApiStub(); - ApiStub::stubResponses([ - 'get_themes', - 'get_theme_assets', - 'get_theme_asset_template_json', - 'get_theme_asset_product_section', - ]); - - // Define config - $this->app['config']->set( - 'shopify-app.theme_support.templates', - [ - 'shop', // Matches fixture data for `templates/shop.json` - ] - ); - - // Run extraction - $this->helper->extractStoreMainTheme($this->shop->getId()); - $jsonFiles = $this->helper->templateJSONFiles(); - $mainSections = $this->helper->mainSections($jsonFiles); - $sectionsWithApp = $this->helper->sectionsWithAppBlock($mainSections); - - $this->assertNotEmpty($sectionsWithApp); - } -} diff --git a/tests/fixtures/empty_theme.json b/tests/fixtures/empty_theme.json new file mode 100644 index 00000000..6299fa3c --- /dev/null +++ b/tests/fixtures/empty_theme.json @@ -0,0 +1,7 @@ +{ + "data": { + "themes": { + "nodes": [] + } + } +} diff --git a/tests/fixtures/empty_with_error.json b/tests/fixtures/empty_with_error.json deleted file mode 100644 index f186dba4..00000000 --- a/tests/fixtures/empty_with_error.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "errors": true -} diff --git a/tests/fixtures/get_theme_asset_product_section.json b/tests/fixtures/get_theme_asset_product_section.json deleted file mode 100644 index 830a45bd..00000000 --- a/tests/fixtures/get_theme_asset_product_section.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "asset": { - "key": "sections/product.liquid", - "public_url": null, - "value": "
\r\n {%- for block in section.blocks -%}\r\n {% render block %}\r\n {%- endfor -%}\r\n<\/div>\r\n\r\n{% schema %}\r\n{\r\n \"name\": \"t:sections.apps.name\",\r\n \"tag\": \"section\",\r\n \"class\": \"section\",\r\n \"settings\": [\r\n {\r\n \"type\": \"checkbox\",\r\n \"id\": \"include_margins\",\r\n \"default\": true,\r\n \"label\": \"t:sections.apps.settings.include_margins.label\"\r\n }\r\n ],\r\n \"blocks\": [\r\n {\r\n \"type\": \"@app\"\r\n }\r\n ],\r\n \"presets\": [\r\n {\r\n \"name\": \"t:sections.apps.presets.name\"\r\n }\r\n ]\r\n}\r\n{% endschema %}", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 1068, - "checksum": null, - "theme_id": 828155753 - } - } diff --git a/tests/fixtures/get_theme_asset_template_json.json b/tests/fixtures/get_theme_asset_template_json.json deleted file mode 100644 index 025710ed..00000000 --- a/tests/fixtures/get_theme_asset_template_json.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "asset": { - "key": "templates/shop.json", - "public_url": null, - "value": "{\"name\":\"Shop template\",\"wrapper\":\"div#div_id.div_class[attribute-one=value]\",\"sections\":{\"main\":{\"type\":\"product\"}},\"order\":[\"main\"]}", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/json", - "size": 1068, - "checksum": null, - "theme_id": 828155753 - } -} diff --git a/tests/fixtures/get_theme_assets.json b/tests/fixtures/get_theme_assets.json deleted file mode 100644 index 067d0a9a..00000000 --- a/tests/fixtures/get_theme_assets.json +++ /dev/null @@ -1,284 +0,0 @@ -{ - "assets": [ - { - "key": "layout/theme.liquid", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 3252, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/sidebar-devider.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/sidebar-devider.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 1016, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/bg-body-pink.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/bg-body-pink.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 1562, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/bg-content.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/bg-content.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 134, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "sections/header_section.liquid", - "public_url": null, - "created_at": "2017-04-28T10:30:00-04:00", - "updated_at": "2017-04-28T10:30:00-04:00", - "content_type": "application/x-liquid", - "size": 998, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/shop.css.liquid", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/shop.css.liquid?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 14675, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/shop.js", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/shop.js?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/javascript", - "size": 348, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/bg-main.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/bg-main.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 297, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "sections/content_section.liquid", - "public_url": null, - "created_at": "2016-02-11T14:31:50-05:00", - "updated_at": "2016-02-11T14:31:50-05:00", - "content_type": "application/x-liquid", - "size": 997, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "templates/page.liquid", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 147, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/bg-body-green.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/bg-body-green.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 1542, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/shop.css", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/shop.css?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "text/css", - "size": 14058, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "config/settings_schema.json", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/json", - "size": 4570, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "sections/product.liquid", - "public_url": null, - "created_at": "2016-02-14T16:31:41-05:00", - "updated_at": "2016-02-14T16:31:41-05:00", - "content_type": "application/x-liquid", - "size": 2440, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "templates/blog.liquid", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 786, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "templates/article.liquid", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 2486, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "templates/product.liquid", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 2796, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "sections/footer_section.liquid", - "public_url": null, - "created_at": "2017-04-28T10:30:00-04:00", - "updated_at": "2017-04-28T10:30:00-04:00", - "content_type": "application/x-liquid", - "size": 999, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/bg-footer.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/bg-footer.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 1434, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/bg-sidebar.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/bg-sidebar.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 124, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/bg-body-orange.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/bg-body-orange.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 1548, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "templates/collection.liquid", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 946, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/sidebar-menu.jpg", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/sidebar-menu.jpg?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/jpeg", - "size": 1609, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "templates/cart.liquid", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 2047, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "config/settings_data.json", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/json", - "size": 4570, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "templates/index.liquid", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/x-liquid", - "size": 1068, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "templates/shop.json", - "public_url": null, - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "application/json", - "size": 1068, - "checksum": null, - "theme_id": 828155753 - }, - { - "key": "assets/bg-body.gif", - "public_url": "https://cdn.shopify.com/s/files/1/0005/4838/0009/t/1/assets/bg-body.gif?v=1278963110", - "created_at": "2010-07-12T15:31:50-04:00", - "updated_at": "2010-07-12T15:31:50-04:00", - "content_type": "image/gif", - "size": 1571, - "checksum": null, - "theme_id": 828155753 - } - ] -} diff --git a/tests/fixtures/get_themes.json b/tests/fixtures/get_themes.json deleted file mode 100644 index 453c7211..00000000 --- a/tests/fixtures/get_themes.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "themes": [ - { - "id": 828155753, - "name": "Comfort", - "created_at": "2022-07-02T01:51:59-04:00", - "updated_at": "2022-07-02T01:51:59-04:00", - "role": "main", - "theme_store_id": null, - "previewable": true, - "processing": false, - "admin_graphql_api_id": "gid://shopify/Theme/828155753" - }, - { - "id": 976877075, - "name": "Preview of Parallax", - "created_at": "2022-07-02T01:51:59-04:00", - "updated_at": "2022-07-02T01:51:59-04:00", - "role": "demo", - "theme_store_id": 688, - "previewable": true, - "processing": false, - "admin_graphql_api_id": "gid://shopify/Theme/976877075" - }, - { - "id": 752253240, - "name": "Sandbox", - "created_at": "2022-07-02T01:51:59-04:00", - "updated_at": "2022-07-02T01:51:59-04:00", - "role": "unpublished", - "theme_store_id": null, - "previewable": true, - "processing": false, - "admin_graphql_api_id": "gid://shopify/Theme/752253240" - } - ] -} diff --git a/tests/fixtures/main_theme.json b/tests/fixtures/main_theme.json new file mode 100644 index 00000000..38a258ad --- /dev/null +++ b/tests/fixtures/main_theme.json @@ -0,0 +1,12 @@ +{ + "data": { + "themes": { + "nodes": [ + { + "id": "gid:\/\/shopify\/OnlineStoreTheme\/123456789", + "name": "Dawn" + } + ] + } + } +} diff --git a/tests/fixtures/theme_sections.json b/tests/fixtures/theme_sections.json new file mode 100644 index 00000000..24d4e1f6 --- /dev/null +++ b/tests/fixtures/theme_sections.json @@ -0,0 +1,16 @@ +{ + "data": { + "theme": { + "files": { + "nodes": [ + { + "filename": "sections\/main-product.liquid", + "body": { + "content": "\n {{ 'section-main-product.css' | asset_url | stylesheet_tag }}\n {{ 'component-accordion.css' | asset_url | stylesheet_tag }}\n {{ 'component-price.css' | asset_url | stylesheet_tag }}\n {{ 'component-slider.css' | asset_url | stylesheet_tag }}\n {{ 'component-rating.css' | asset_url | stylesheet_tag }}\n {{ 'component-deferred-media.css' | asset_url | stylesheet_tag }}\n\n {% unless product.has_only_default_variant %}\n {{ 'component-product-variant-picker.css' | asset_url | stylesheet_tag }}\n {{ 'component-swatch-input.css' | asset_url | stylesheet_tag }}\n {{ 'component-swatch.css' | asset_url | stylesheet_tag }}\n {% endunless %}\n {%- if product.quantity_price_breaks_configured? -%}\n {{ 'component-volume-pricing.css' | asset_url | stylesheet_tag }}\n {%- endif -%}\n\n {%- style -%}\n .section-{{ section.id }}-padding {\n padding-top: {{ section.settings.padding_top | times: 0.75 | round: 0 }}px;\n padding-bottom: {{ section.settings.padding_bottom | times: 0.75 | round: 0 }}px;\n }\n\n @media screen and (min-width: 750px) {\n .section-{{ section.id }}-padding {\n padding-top: {{ section.settings.padding_top }}px;\n padding-bottom: {{ section.settings.padding_bottom }}px;\n }\n }\n {%- endstyle -%}\n\n