Skip to content

Commit 113fa3c

Browse files
Fix oneOf and add tests
1 parent d95f396 commit 113fa3c

3 files changed

Lines changed: 44 additions & 1 deletion

File tree

src/Directives/BuiltIn/OneOf.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
/**
1717
* Binds `#[OneOf]` to webonyx's built-in `@oneOf` directive. Putting it on an `#[Input]` class sets
1818
* the input object's `isOneOf` flag, which makes validation require one field and gets webonyx to
19-
* print `@oneOf` in the SDL.
19+
* print `@oneOf` in the SDL. The flag also tells {@see MutableInputObjectType::getFields()} to drop
20+
* field default values, which OneOf inputs must not have (they break variable coercion otherwise).
2021
*
2122
* webonyx already defines `@oneOf`, so we don't register our own definition for it
2223
* ({@see DirectiveDefinition::$builtIn} is `true`).

src/Types/MutableInputObjectType.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ public function getFields(): array
105105
if (empty($this->finalFields)) {
106106
throw NoFieldsException::create($this->name);
107107
}
108+
109+
if ($this->isOneOf) {
110+
foreach ($this->finalFields as $field) {
111+
unset($field->config['defaultValue']);
112+
$field->defaultValue = null;
113+
}
114+
}
108115
}
109116

110117
return $this->finalFields;

tests/Integration/DirectiveLayerEndToEndTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,26 @@ public function testOneOfAcceptsExactlyOneField(): void
109109
$this->assertSame(['data' => ['lookup' => 'abc']], $result);
110110
}
111111

112+
public function testOneOfResolvesTheOtherBranch(): void
113+
{
114+
$result = $this->execute('query { lookup(lookup: {id: 1}) }');
115+
116+
$this->assertSame(['data' => ['lookup' => '1']], $result);
117+
}
118+
119+
public function testOneOfAcceptsInputPassedThroughAVariable(): void
120+
{
121+
$result = GraphQL::executeQuery(
122+
$this->schema,
123+
'query ($lookup: LookupInput!) { lookup(lookup: $lookup) }',
124+
null,
125+
new Context(),
126+
['lookup' => ['sku' => 'abc']],
127+
)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE);
128+
129+
$this->assertSame(['data' => ['lookup' => 'abc']], $result);
130+
}
131+
112132
public function testOneOfRejectsMoreThanOneField(): void
113133
{
114134
$result = $this->execute('query { lookup(lookup: {sku: "abc", id: 1}) }');
@@ -130,4 +150,19 @@ public function testOneOfRejectsZeroFields(): void
130150
$result['errors'][0]['message'],
131151
);
132152
}
153+
154+
/**
155+
* Passing a single field explicitly set to null is distinct from passing no fields: the one field
156+
* is present but its value is null, which a OneOf input still forbids.
157+
*/
158+
public function testOneOfRejectsAnExplicitNullField(): void
159+
{
160+
$result = $this->execute('query { lookup(lookup: {sku: null}) }');
161+
162+
$this->assertArrayNotHasKey('data', $result);
163+
$this->assertStringContainsString(
164+
"OneOf input object 'LookupInput' field 'sku' must be non-null",
165+
$result['errors'][0]['message'],
166+
);
167+
}
133168
}

0 commit comments

Comments
 (0)