Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

(graphcache) - Add missing partial mark to null cascading into list (#1869)

* (graphcache) - Add missing partial mark to null cascading into list

* Add changeset

* Add test for behaviour

authored by

Phil Pluckthun and committed by
GitHub
33f122c8 d8bcc88a

+124 -1
+5
.changeset/chilled-pears-eat.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Fix missing values cascading into lists causing a `null` item without the query being marked as stale and fetched from the API. This would happen in schema awareness when a required field, which isn't cached, cascades into a nullable list.
+116
exchanges/graphcache/src/cacheExchange.test.ts
··· 1554 1554 'partial' 1555 1555 ); 1556 1556 }); 1557 + 1558 + it('reexecutes query and returns data on partial results for nullable lists', () => { 1559 + jest.useFakeTimers(); 1560 + const client = createClient({ url: 'http://0.0.0.0' }); 1561 + const { source: ops$, next } = makeSubject<Operation>(); 1562 + const reexec = jest 1563 + .spyOn(client, 'reexecuteOperation') 1564 + // Empty mock to avoid going in an endless loop, since we would again return 1565 + // partial data. 1566 + .mockImplementation(() => undefined); 1567 + 1568 + const initialQuery = gql` 1569 + query { 1570 + todos { 1571 + id 1572 + __typename 1573 + } 1574 + } 1575 + `; 1576 + 1577 + const query = gql` 1578 + query { 1579 + todos { 1580 + id 1581 + text 1582 + __typename 1583 + } 1584 + } 1585 + `; 1586 + 1587 + const initialQueryOperation = client.createRequestOperation('query', { 1588 + key: 1, 1589 + query: initialQuery, 1590 + }); 1591 + 1592 + const queryOperation = client.createRequestOperation('query', { 1593 + key: 2, 1594 + query, 1595 + }); 1596 + 1597 + const queryData = { 1598 + __typename: 'Query', 1599 + todos: [ 1600 + { 1601 + __typename: 'Todo', 1602 + id: '123', 1603 + }, 1604 + { 1605 + __typename: 'Todo', 1606 + id: '456', 1607 + }, 1608 + ], 1609 + }; 1610 + 1611 + const response = jest.fn( 1612 + (forwardOp: Operation): OperationResult => { 1613 + if (forwardOp.key === 1) { 1614 + return { operation: initialQueryOperation, data: queryData }; 1615 + } else if (forwardOp.key === 2) { 1616 + return { operation: queryOperation, data: queryData }; 1617 + } 1618 + 1619 + return undefined as any; 1620 + } 1621 + ); 1622 + 1623 + const result = jest.fn(); 1624 + const forward: ExchangeIO = ops$ => pipe(ops$, delay(1), map(response)); 1625 + 1626 + pipe( 1627 + cacheExchange({ 1628 + schema: minifyIntrospectionQuery( 1629 + // eslint-disable-next-line 1630 + require('./test-utils/simple_schema.json') 1631 + ), 1632 + })({ forward, client, dispatchDebug })(ops$), 1633 + tap(result), 1634 + publish 1635 + ); 1636 + 1637 + next(initialQueryOperation); 1638 + jest.runAllTimers(); 1639 + expect(response).toHaveBeenCalledTimes(1); 1640 + expect(reexec).toHaveBeenCalledTimes(0); 1641 + expect(result.mock.calls[0][0].data).toMatchObject({ 1642 + todos: [ 1643 + { 1644 + __typename: 'Todo', 1645 + id: '123', 1646 + }, 1647 + { 1648 + __typename: 'Todo', 1649 + id: '456', 1650 + }, 1651 + ], 1652 + }); 1653 + 1654 + expect(result.mock.calls[0][0]).toHaveProperty( 1655 + 'operation.context.meta', 1656 + undefined 1657 + ); 1658 + 1659 + next(queryOperation); 1660 + jest.runAllTimers(); 1661 + expect(result).toHaveBeenCalledTimes(2); 1662 + expect(reexec).toHaveBeenCalledTimes(1); 1663 + expect(result.mock.calls[1][0].stale).toBe(true); 1664 + expect(result.mock.calls[1][0].data).toEqual({ 1665 + todos: [null, null], 1666 + }); 1667 + 1668 + expect(result.mock.calls[1][0]).toHaveProperty( 1669 + 'operation.context.meta.cacheOutcome', 1670 + 'partial' 1671 + ); 1672 + }); 1557 1673 }); 1558 1674 1559 1675 describe('commutativity', () => {
+3 -1
exchanges/graphcache/src/operations/query.ts
··· 492 492 if (childResult === undefined && !_isListNullable) { 493 493 return undefined; 494 494 } else { 495 - data[i] = childResult !== undefined ? childResult : null; 495 + ctx.partial = ctx.partial || _isListNullable; 496 + data[i] = childResult != null ? childResult : null; 496 497 hasChanged = hasChanged || data[i] !== prevData![i]; 497 498 } 498 499 } ··· 556 557 if (childLink === undefined && !_isListNullable) { 557 558 return undefined; 558 559 } else { 560 + ctx.partial = ctx.partial || _isListNullable; 559 561 newLink[i] = childLink || null; 560 562 hasChanged = hasChanged || newLink[i] !== prevData![i]; 561 563 }