get_type() ? $product->get_default_attributes() : $product->get_attributes(); $expected = []; foreach ( $attributes as $name => $value ) { $term = \get_term_by( 'slug', $value, $name ); $expected_id = base64_encode( $id . '||' . $name . '||' . $value ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode if ( ! $term instanceof \WP_Term ) { $expected[] = $this->expectedNode( 'nodes', [ $this->expectedField( 'id', $expected_id ), $this->expectedField( 'attributeId', 0 ), $this->expectedField( 'name', $name ), $this->expectedField( 'value', $value ), ] ); } else { $expected[] = $this->expectedNode( 'nodes', [ $this->expectedField( 'id', $expected_id ), $this->expectedField( 'attributeId', $term->term_id ), $this->expectedField( 'name', $term->taxonomy ), $this->expectedField( 'value', $term->name ), ] ); } } return $expected; } public function expectedDefaultAttributes( $id ) { $product = wc_get_product( $id ); $attributes = $product->get_attributes(); $expected = []; foreach ( $attributes as $attribute ) { $name = $attribute->get_name(); if ( $attribute->is_taxonomy() ) { $attribute_values = wc_get_product_terms( $id, $attribute->get_name(), [ 'fields' => 'all' ] ); foreach ( $attribute_values as $attribute_value ) { $expected[] = $this->expectedNode( 'nodes', [ $this->expectedField( 'id', base64_encode( $id . '|' . $name . '|' . $attribute_value->name ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $this->expectedField( 'attributeId', $attribute_value->term_id ), $this->expectedField( 'name', $name ), $this->expectedField( 'value', $attribute_value->name ), ] ); } } else { $values = $attribute->get_options(); foreach ( $values as $attribute_value ) { $expected[] = $this->expectedNode( 'nodes', [ $this->expectedField( 'id', base64_encode( $id . '|' . $name . '|' . $attribute_value ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $this->expectedField( 'attributeId', 0 ), $this->expectedField( 'name', $name ), $this->expectedField( 'value', $attribute_value ), ] ); } } } return $expected; } // tests public function testProductVariationToVariationAttributeQuery() { // Create a product and variation. $product_ids = $this->factory->product_variation->createSome(); $variation_id = $product_ids['variations'][0]; // Create a query. $query = ' query fromVariationQuery( $id: ID! ) { productVariation( id: $id ) { id attributes { nodes { id attributeId name value } } } } '; /** * Assertion One * * Test query and results */ $variables = [ 'id' => $this->toRelayId( 'post', $variation_id ) ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $expected = [ $this->expectedField( 'productVariation.id', $this->toRelayId( 'post', $variation_id ) ), $this->expectedObject( 'productVariation.attributes', $this->expectedAttributes( $variation_id ) ), ]; $this->assertQuerySuccessful( $response, $expected ); } public function testVariableProductToVariationAttributeQuery() { // Create a variable product w/ default attributes.. $product_ids = $this->factory->product_variation->createSome(); $product_id = $product_ids['product']; // Create a query. $query = ' query ( $id: ID! ) { product( id: $id ) { ... on VariableProduct { id defaultAttributes { nodes { id attributeId name value } } } } } '; /** * Assertion One * * Test query and results */ $variables = [ 'id' => $this->toRelayId( 'post', $product_id ) ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $expected = [ $this->expectedField( 'product.id', $this->toRelayId( 'post', $product_id ) ), $this->expectedObject( 'product.defaultAttributes', $this->expectedAttributes( $product_id ) ), ]; $this->assertQuerySuccessful( $response, $expected ); } public function testSimpleProductToVariationAttributeQuery() { // Create a product w/ default attributes. $attribute_data = [ $this->factory->product->createAttribute( 'size', [ 'small' ] ), $this->factory->product->createAttribute( 'color', [ 'red' ] ), [ 'attribute_id' => 0, 'attribute_taxonomy' => 'logo', 'term_ids' => [ 'Yes' ], ], ]; $attributes = array_map( static function ( $data, $index ) { $attribute = new \WC_Product_Attribute(); $attribute->set_id( $data['attribute_id'] ); $attribute->set_name( $data['attribute_taxonomy'] ); $attribute->set_options( $data['term_ids'] ); $attribute->set_position( $index ); $attribute->set_visible( true ); $attribute->set_variation( false ); return $attribute; }, $attribute_data, array_keys( $attribute_data ) ); $product_id = $this->factory->product->createSimple( [ 'attributes' => $attributes, 'default_attributes' => [ 'pa_size' => 'small', 'pa_color' => 'red', 'logo' => 'Yes', ], ] ); // Create a query. $query = ' query ( $id: ID! ) { product( id: $id ) { ... on SimpleProduct { id defaultAttributes { nodes { id attributeId name value } } } } } '; /** * Assertion One * * Test query and results */ $variables = [ 'id' => $this->toRelayId( 'post', $product_id ) ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $expected = [ $this->expectedField( 'product.id', $this->toRelayId( 'post', $product_id ) ), $this->expectedObject( 'product.defaultAttributes', $this->expectedDefaultAttributes( $product_id ) ), ]; $this->assertQuerySuccessful( $response, $expected ); } /** * Test that VariationAttribute.label returns human-readable values. * * Global attributes should return the term name (e.g., "Small" not "small"). * Local attributes should return the attribute value (e.g., "Yes" not "logo"). * * @see https://github.com/wp-graphql/wp-graphql-woocommerce/issues/965 */ public function testVariationAttributeLabelReturnsHumanReadableValues() { $product_ids = $this->factory->product_variation->createSome(); $variation_id = $product_ids['variations'][0]; $query = ' query ($id: ID!) { productVariation(id: $id) { attributes { nodes { name label value } } } } '; $variables = [ 'id' => $this->toRelayId( 'post', $variation_id ) ]; $response = $this->graphql( compact( 'query', 'variables' ) ); // Build expected labels from the variation's attributes. $product = wc_get_product( $variation_id ); $attributes = $product->get_attributes(); $expected = []; foreach ( $attributes as $name => $value ) { $term = get_term_by( 'slug', $value, $name ); if ( $term instanceof \WP_Term ) { // Global attribute: label should be the human-readable attribute label. $expected[] = $this->expectedNode( 'productVariation.attributes.nodes', [ $this->expectedField( 'name', $term->taxonomy ), $this->expectedField( 'label', wc_attribute_label( $term->taxonomy ) ), $this->expectedField( 'value', $term->slug ), ] ); } else { // Local attribute: label should be the attribute name. $expected[] = $this->expectedNode( 'productVariation.attributes.nodes', [ $this->expectedField( 'name', $name ), $this->expectedField( 'label', $name ), $this->expectedField( 'value', $value ), ] ); } } $this->assertQuerySuccessful( $response, $expected ); } /** * Test that querying variation attributes returns empty nodes when * the variation has null attributes (e.g. corrupted or missing data). * * @see https://github.com/wp-graphql/wp-graphql-woocommerce/issues/1011 */ public function testVariationWithNullAttributesReturnsEmptyNodes() { // Create a variable product with no attributes, then create a bare variation. $product_id = $this->factory->product->createVariable( [ 'attribute_data' => [] ] ); $variation_id = $this->factory->product_variation->create( [ 'parent_id' => $product_id, 'regular_price' => 10, ] ); // Confirm WooCommerce returns empty attributes for this variation. $variation = wc_get_product( $variation_id ); $this->assertEmpty( $variation->get_attributes(), 'Variation should have no attributes.' ); $query = ' query ($id: ID!) { productVariation(id: $id) { id attributes { nodes { id name value } } } } '; $variables = [ 'id' => $this->toRelayId( 'post', $variation_id ) ]; $response = $this->graphql( compact( 'query', 'variables' ) ); // The query should succeed without PHP warnings or errors, // and attributes should be null (no populated attribute data). $this->assertQuerySuccessful( $response, [ $this->expectedField( 'productVariation.id', $this->toRelayId( 'post', $variation_id ) ), $this->expectedField( 'productVariation.attributes', static::IS_NULL ), ] ); } }