2019-05-02 13:28:04 -04:00
|
|
|
<?php
|
|
|
|
|
|
2023-02-02 16:47:19 -05:00
|
|
|
class ProductAttributeQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
|
|
|
|
|
public function expectedProductAttributeData( $product_id, $path ) {
|
|
|
|
|
$product = wc_get_product( $product_id );
|
|
|
|
|
$attributes = $product->get_attributes();
|
2019-05-02 13:28:04 -04:00
|
|
|
|
2023-02-02 16:47:19 -05:00
|
|
|
$expected = [];
|
2019-05-02 13:28:04 -04:00
|
|
|
|
2023-02-02 16:47:19 -05:00
|
|
|
foreach ( $attributes as $attribute_name => $attribute ) {
|
2026-03-23 14:55:56 -04:00
|
|
|
if ( $attribute->is_taxonomy() ) {
|
|
|
|
|
$expected_id = \GraphQLRelay\Relay::toGlobalId( 'GlobalProductAttribute', $attribute->get_id() );
|
|
|
|
|
} else {
|
|
|
|
|
$expected_id = \GraphQLRelay\Relay::toGlobalId( 'LocalProductAttribute', $attribute->get_name() . ':' . $product_id );
|
|
|
|
|
}
|
2023-02-02 16:47:19 -05:00
|
|
|
$expected[] = $this->expectedNode(
|
|
|
|
|
$path,
|
|
|
|
|
[
|
2026-03-23 14:55:56 -04:00
|
|
|
$this->expectedField( 'id', $expected_id ),
|
2023-02-02 16:47:19 -05:00
|
|
|
$this->expectedField( 'attributeId', $attribute->get_id() ),
|
2023-07-19 19:13:51 -04:00
|
|
|
$this->expectedField( 'name', $attribute->get_name() ),
|
2023-02-02 16:47:19 -05:00
|
|
|
$this->expectedField(
|
|
|
|
|
'label',
|
|
|
|
|
$attribute->is_taxonomy()
|
2024-08-07 02:40:46 +02:00
|
|
|
? get_taxonomy( $attribute->get_name() )->labels->singular_name
|
|
|
|
|
: $attribute->get_name()
|
2023-02-02 16:47:19 -05:00
|
|
|
),
|
|
|
|
|
$this->expectedField( 'options', $attribute->get_slugs() ),
|
|
|
|
|
$this->expectedField( 'position', $attribute->get_position() ),
|
|
|
|
|
$this->expectedField( 'visible', $attribute->get_visible() ),
|
|
|
|
|
$this->expectedField( 'variation', $attribute->get_variation() ),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-07-06 16:33:48 -04:00
|
|
|
|
2023-02-02 16:47:19 -05:00
|
|
|
return $expected;
|
2022-02-04 15:00:44 -05:00
|
|
|
}
|
2019-05-02 13:28:04 -04:00
|
|
|
|
2022-02-04 15:00:44 -05:00
|
|
|
// tests
|
|
|
|
|
public function testProductAttributeQuery() {
|
2023-02-02 16:47:19 -05:00
|
|
|
$product_id = $this->factory->product->createVariable();
|
|
|
|
|
$variation_ids = $this->factory->product_variation->createSome( $product_id )['variations'];
|
|
|
|
|
|
2022-02-04 15:00:44 -05:00
|
|
|
$query = '
|
2019-05-02 13:28:04 -04:00
|
|
|
query attributeQuery( $id: ID! ) {
|
|
|
|
|
product( id: $id ) {
|
2019-10-17 21:39:38 -04:00
|
|
|
... on VariableProduct {
|
|
|
|
|
id
|
|
|
|
|
attributes {
|
|
|
|
|
nodes {
|
|
|
|
|
id
|
|
|
|
|
attributeId
|
2020-07-06 16:33:48 -04:00
|
|
|
name
|
2020-08-06 14:46:34 -04:00
|
|
|
label
|
2019-10-17 21:39:38 -04:00
|
|
|
options
|
|
|
|
|
position
|
|
|
|
|
visible
|
|
|
|
|
variation
|
|
|
|
|
}
|
2019-05-02 13:28:04 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
';
|
|
|
|
|
|
2024-08-06 16:54:18 -06:00
|
|
|
$variables = [ 'id' => $this->toRelayId( 'post', $product_id ) ];
|
2023-02-02 16:47:19 -05:00
|
|
|
$response = $this->graphql( compact( 'query', 'variables' ) );
|
|
|
|
|
$expected = array_merge(
|
2024-08-06 16:54:18 -06:00
|
|
|
[ $this->expectedField( 'product.id', $this->toRelayId( 'post', $product_id ) ) ],
|
2023-02-02 16:47:19 -05:00
|
|
|
$this->expectedProductAttributeData( $product_id, 'product.attributes.nodes' )
|
2022-02-04 15:00:44 -05:00
|
|
|
);
|
2019-05-02 13:28:04 -04:00
|
|
|
|
2023-02-02 16:47:19 -05:00
|
|
|
$this->assertQuerySuccessful( $response, $expected );
|
2022-02-04 15:00:44 -05:00
|
|
|
}
|
2019-05-02 13:28:04 -04:00
|
|
|
|
2022-02-04 15:00:44 -05:00
|
|
|
public function testProductAttributeToProductConnectionQuery() {
|
2023-08-22 17:14:39 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create noise products.
|
|
|
|
|
$product_id = $this->factory->product->createVariable(
|
|
|
|
|
[
|
|
|
|
|
'attribute_data' => [ $this->factory->product->createAttribute( 'pattern', [ 'polka-dot', 'stripe', 'flames' ] ) ],
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
$variation_id = $this->factory->product_variation->create(
|
|
|
|
|
[
|
|
|
|
|
'parent_id' => $product_id,
|
|
|
|
|
'attributes' => [
|
|
|
|
|
'pattern' => 'polka-dot',
|
|
|
|
|
],
|
|
|
|
|
'image_id' => null,
|
|
|
|
|
'regular_price' => 10,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Create variable product with attribute.
|
|
|
|
|
$other_product_id = $this->factory->product->createVariable();
|
|
|
|
|
$other_variation_id = $this->factory->product_variation->create(
|
|
|
|
|
[
|
|
|
|
|
'parent_id' => $product_id,
|
|
|
|
|
'attributes' => [
|
|
|
|
|
'pattern' => 'stripe',
|
|
|
|
|
],
|
|
|
|
|
'image_id' => null,
|
|
|
|
|
'regular_price' => 10,
|
|
|
|
|
]
|
2023-11-30 15:41:32 +02:00
|
|
|
);
|
2023-08-22 17:14:39 -04:00
|
|
|
$other_product_id_2 = $this->factory->product->createSimple();
|
|
|
|
|
$this->clearSchema();
|
2023-02-02 16:47:19 -05:00
|
|
|
|
2022-02-04 15:00:44 -05:00
|
|
|
$query = '
|
2023-08-22 17:14:39 -04:00
|
|
|
query attributeConnectionQuery( $pattern: [String!] ) {
|
|
|
|
|
allPaPattern( where: { name: $pattern } ) {
|
2019-05-02 13:28:04 -04:00
|
|
|
nodes {
|
|
|
|
|
products {
|
|
|
|
|
nodes {
|
2023-08-22 17:14:39 -04:00
|
|
|
id
|
2019-05-02 13:28:04 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
';
|
|
|
|
|
|
2023-08-22 17:14:39 -04:00
|
|
|
/**
|
|
|
|
|
* Assert correct products are queried.
|
|
|
|
|
*/
|
|
|
|
|
$variables = [ 'pattern' => 'polka-dot' ];
|
2023-02-02 16:47:19 -05:00
|
|
|
$response = $this->graphql( compact( 'query', 'variables' ) );
|
2022-08-26 14:53:36 -04:00
|
|
|
$expected = [
|
2024-08-06 16:54:18 -06:00
|
|
|
$this->expectedField( 'allPaPattern.nodes.0.products.nodes.0.id', $this->toRelayId( 'post', $product_id ) ),
|
2022-08-26 14:53:36 -04:00
|
|
|
];
|
2019-05-02 13:28:04 -04:00
|
|
|
|
2023-02-02 16:47:19 -05:00
|
|
|
$this->assertQuerySuccessful( $response, $expected );
|
2022-02-04 15:00:44 -05:00
|
|
|
}
|
2019-05-02 13:28:04 -04:00
|
|
|
|
2022-02-04 15:00:44 -05:00
|
|
|
public function testProductAttributeToVariationConnectionQuery() {
|
2023-02-02 16:47:19 -05:00
|
|
|
$product_id = $this->factory->product->createVariable();
|
|
|
|
|
$variation_ids = $this->factory->product_variation->createSome( $product_id )['variations'];
|
|
|
|
|
|
2022-02-04 15:00:44 -05:00
|
|
|
$query = '
|
2019-05-02 13:28:04 -04:00
|
|
|
query attributeConnectionQuery( $size: [String!] ) {
|
2022-02-04 15:00:44 -05:00
|
|
|
allPaSize( where: { name: $size } ) {
|
2019-05-02 13:28:04 -04:00
|
|
|
nodes {
|
|
|
|
|
variations {
|
|
|
|
|
nodes {
|
|
|
|
|
id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
';
|
|
|
|
|
|
2022-08-26 14:53:36 -04:00
|
|
|
$variables = [ 'size' => 'small' ];
|
2023-02-02 16:47:19 -05:00
|
|
|
$response = $this->graphql( compact( 'query', 'variables' ) );
|
|
|
|
|
$expected = array_map(
|
2023-11-30 15:41:32 +02:00
|
|
|
function ( $id ) {
|
2024-08-06 16:54:18 -06:00
|
|
|
return $this->expectedField( 'allPaSize.nodes.0.variations.nodes.#.id', $this->toRelayId( 'post', $id ) );
|
2023-02-02 16:47:19 -05:00
|
|
|
},
|
|
|
|
|
array_filter(
|
|
|
|
|
$variation_ids,
|
2023-11-30 15:41:32 +02:00
|
|
|
static function ( $id ) {
|
2023-02-02 16:47:19 -05:00
|
|
|
$variation = new \WC_Product_Variation( $id );
|
|
|
|
|
$small_attribute = array_filter(
|
|
|
|
|
$variation->get_attributes(),
|
2023-11-30 15:41:32 +02:00
|
|
|
static function ( $attribute ) {
|
2023-02-02 16:47:19 -05:00
|
|
|
return 'small' === $attribute;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return ! empty( $small_attribute );
|
|
|
|
|
}
|
|
|
|
|
)
|
2022-02-04 15:00:44 -05:00
|
|
|
);
|
2019-05-02 13:28:04 -04:00
|
|
|
|
2023-02-02 16:47:19 -05:00
|
|
|
$this->assertQuerySuccessful( $response, $expected );
|
2022-02-04 15:00:44 -05:00
|
|
|
}
|
2024-08-07 02:40:46 +02:00
|
|
|
|
|
|
|
|
public function testProductAttributeMatchesVariationAttributeCounterpart() {
|
|
|
|
|
$product_id = $this->factory->product->createVariable();
|
|
|
|
|
$variation_ids = $this->factory->product_variation->createSome( $product_id )['variations'];
|
|
|
|
|
|
|
|
|
|
$query = '
|
|
|
|
|
query attributeQuery( $id: ID! ) {
|
|
|
|
|
product( id: $id ) {
|
|
|
|
|
id
|
2024-11-06 11:35:14 -05:00
|
|
|
... on ProductWithAttributes {
|
|
|
|
|
attributes {
|
|
|
|
|
nodes {
|
|
|
|
|
name
|
|
|
|
|
label
|
|
|
|
|
options
|
|
|
|
|
}
|
2024-08-07 02:40:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
... on ProductWithVariations {
|
|
|
|
|
variations {
|
|
|
|
|
nodes {
|
|
|
|
|
id
|
|
|
|
|
attributes {
|
|
|
|
|
nodes {
|
|
|
|
|
name
|
|
|
|
|
label
|
|
|
|
|
value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
';
|
|
|
|
|
|
|
|
|
|
$variables = [ 'id' => $this->toRelayId( 'post', $product_id ) ];
|
|
|
|
|
$response = $this->graphql( compact( 'query', 'variables' ) );
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Assert that the product attributes match the variation attributes
|
|
|
|
|
* without modification to confirm variations can be identified by product attribute.
|
|
|
|
|
*/
|
|
|
|
|
$attributes = $this->lodashGet( $response, 'data.product.attributes.nodes', [] );
|
|
|
|
|
$variations = $this->lodashGet( $response, 'data.product.variations.nodes', [] );
|
|
|
|
|
|
|
|
|
|
foreach( $variations as $variation ) {
|
|
|
|
|
$variation_attributes = $this->lodashGet( $variation, 'attributes.nodes', [] );
|
|
|
|
|
foreach( $variation_attributes as $variation_attribute ) {
|
|
|
|
|
$attribute_name = $variation_attribute['name'];
|
|
|
|
|
$attribute = array_search( $attribute_name, array_column( $attributes, 'name' ) );
|
|
|
|
|
$this->assertNotFalse( $attribute, sprintf( 'Variation attribute not found in product attributes for %s', $attribute_name ) );
|
|
|
|
|
if ( "" === $variation_attribute['value'] ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertContains( $variation_attribute['value'], $attributes[ $attribute ]['options'] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertQuerySuccessful(
|
|
|
|
|
$response,
|
|
|
|
|
[ $this->expectedField( 'product.id', $this->toRelayId( 'post', $product_id ) ) ]
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-03-23 14:55:56 -04:00
|
|
|
|
|
|
|
|
public function testProductAttributesQuery() {
|
|
|
|
|
$this->factory->product->createAttribute( 'texture', [ 'smooth', 'rough', 'tiled' ] );
|
|
|
|
|
$this->factory->product->createAttribute( 'tile-size', [ '4x4', '8x8', '12x12' ] );
|
|
|
|
|
|
|
|
|
|
$query = '
|
|
|
|
|
query {
|
|
|
|
|
productAttributes {
|
|
|
|
|
nodes {
|
|
|
|
|
id
|
|
|
|
|
attributeId
|
|
|
|
|
name
|
|
|
|
|
label
|
|
|
|
|
options
|
|
|
|
|
position
|
|
|
|
|
visible
|
|
|
|
|
variation
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
';
|
|
|
|
|
|
|
|
|
|
$response = $this->graphql( compact( 'query' ) );
|
|
|
|
|
$expected = [
|
|
|
|
|
$this->expectedNode(
|
|
|
|
|
'productAttributes.nodes',
|
|
|
|
|
[
|
|
|
|
|
$this->expectedField( 'id', self::NOT_NULL ),
|
|
|
|
|
$this->expectedField( 'name', 'pa_texture' ),
|
|
|
|
|
$this->expectedField( 'label', 'texture' ),
|
|
|
|
|
$this->expectedField( 'options', [ 'rough', 'smooth', 'tiled' ] ),
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
$this->expectedNode(
|
|
|
|
|
'productAttributes.nodes',
|
|
|
|
|
[
|
|
|
|
|
$this->expectedField( 'id', self::NOT_NULL ),
|
|
|
|
|
$this->expectedField( 'name', 'pa_tile-size' ),
|
|
|
|
|
$this->expectedField( 'label', 'tile-size' ),
|
|
|
|
|
$this->expectedField( 'options', [ '12x12', '4x4', '8x8' ] ),
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$this->assertQuerySuccessful( $response, $expected );
|
|
|
|
|
}
|
2026-04-01 13:17:30 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that registering a global product attribute with no terms
|
|
|
|
|
* does not break the GraphQL schema or server.
|
|
|
|
|
*/
|
|
|
|
|
public function testGlobalProductAttributeWithNoTermsDoesNotBreakSchema() {
|
|
|
|
|
// Register a global product attribute with no terms.
|
|
|
|
|
$this->factory->product->createAttribute( 'material', [] );
|
|
|
|
|
$this->clearSchema();
|
|
|
|
|
|
|
|
|
|
// Validate the schema is still valid.
|
|
|
|
|
$schema = \WPGraphQL::get_schema();
|
|
|
|
|
$schema->assertValid();
|
|
|
|
|
|
|
|
|
|
// Run an introspection query to confirm the type registry is intact.
|
|
|
|
|
$query = \GraphQL\Type\Introspection::getIntrospectionQuery();
|
|
|
|
|
$response = $this->graphql( compact( 'query' ) );
|
|
|
|
|
|
|
|
|
|
$this->assertQuerySuccessful( $response, [] );
|
|
|
|
|
|
|
|
|
|
// Query the empty attribute taxonomy directly.
|
|
|
|
|
$query = '
|
|
|
|
|
query {
|
|
|
|
|
allPaMaterial {
|
|
|
|
|
nodes {
|
|
|
|
|
name
|
|
|
|
|
slug
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
';
|
|
|
|
|
|
|
|
|
|
$response = $this->graphql( compact( 'query' ) );
|
|
|
|
|
|
|
|
|
|
$this->assertQuerySuccessful(
|
|
|
|
|
$response,
|
|
|
|
|
[
|
|
|
|
|
$this->expectedField( 'allPaMaterial.nodes', static::IS_FALSY ),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Confirm productAttributes root query includes the empty attribute.
|
|
|
|
|
$query = '
|
|
|
|
|
query {
|
|
|
|
|
productAttributes {
|
|
|
|
|
nodes {
|
|
|
|
|
name
|
|
|
|
|
label
|
|
|
|
|
options
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
';
|
|
|
|
|
|
|
|
|
|
$response = $this->graphql( compact( 'query' ) );
|
|
|
|
|
|
|
|
|
|
$this->assertQuerySuccessful(
|
|
|
|
|
$response,
|
|
|
|
|
[
|
|
|
|
|
$this->expectedNode(
|
|
|
|
|
'productAttributes.nodes',
|
|
|
|
|
[
|
|
|
|
|
$this->expectedField( 'name', 'pa_material' ),
|
|
|
|
|
$this->expectedField( 'label', 'material' ),
|
|
|
|
|
$this->expectedField( 'options', [] ),
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-07-06 16:33:48 -04:00
|
|
|
}
|