Skip to content

Commit

Permalink
feat: SB-748 migrate subscripion plans all query
Browse files Browse the repository at this point in the history
* feat: SB-748 migrate subscripion plans all query

* feat: SB-748 adjust test to new fragment


Approved-by: Michał Kleszcz
  • Loading branch information
sdrejkarz committed Feb 10, 2023
1 parent 1891836 commit 0892159
Show file tree
Hide file tree
Showing 16 changed files with 199 additions and 246 deletions.
20 changes: 13 additions & 7 deletions packages/webapp/src/mocks/factories/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MockPayloadGenerator, RelayMockEnvironment } from 'relay-test-utils';

import SubscriptionActivePlanDetailsQuery from '../../modules/subscription/__generated__/subscriptionActivePlanDetailsQuery.graphql';
import subscriptionPlansAllQueryGraphql from '../../modules/subscription/__generated__/subscriptionPlansAllQuery.graphql';
import { SUBSCRIPTION_PLANS_ALL_QUERY } from '../../routes/finances/editSubscription/subscriptionPlans/subscriptionPlans.graphql';
import { STRIPE_SUBSCRIPTION_QUERY } from '../../shared/components/finances/stripe/stripePaymentMethodSelector/stripePaymentMethodSelector.graphql';
import { SUBSCRIPTION_ACTIVE_PLAN_DETAILS_QUERY } from '../../shared/hooks/finances/useSubscriptionPlanDetails/useSubscriptionPlanDetails.graphql';
import {
Expand Down Expand Up @@ -96,16 +97,21 @@ export const fillSubscriptionScheduleQueryWithPhases = (
};

export const fillSubscriptionPlansAllQuery = (env: RelayMockEnvironment, data: SubscriptionPlan[] = []) => {
env.mock.queueOperationResolver((operation: OperationDescriptor) =>
MockPayloadGenerator.generate(operation, {
SubscriptionPlanConnection: () => connectionFromArray(data),
})
);
env.mock.queuePendingOperation(subscriptionPlansAllQueryGraphql, {});
if (env) {
env.mock.queueOperationResolver((operation: OperationDescriptor) =>
MockPayloadGenerator.generate(operation, {
SubscriptionPlanConnection: () => connectionFromArray(data),
})
);
env.mock.queuePendingOperation(subscriptionPlansAllQueryGraphql, {});
}

return composeMockedListQueryResult(SUBSCRIPTION_PLANS_ALL_QUERY, 'allSubscriptionPlans', 'SubscriptionPlanType', {
data,
});
};

// Apollo Mocks

export const fillAllPaymentsMethodsQuery = (data: Partial<Subscription>[]) =>
composeMockedListQueryResult(STRIPE_SUBSCRIPTION_QUERY, 'allPaymentMethods', 'StripePaymentMethodType', {
data,
Expand Down
25 changes: 0 additions & 25 deletions packages/webapp/src/modules/subscription/subscription.queries.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,5 @@
import graphql from 'babel-plugin-relay/macro';

graphql`
fragment subscriptionPlanItemFragment on SubscriptionPlanType {
id
pk
product {
id
name
}
unitAmount
}
`;

graphql`
query subscriptionPlansAllQuery {
allSubscriptionPlans(first: 100) {
edges {
node {
id
...subscriptionPlanItemFragment
}
}
}
}
`;

graphql`
query subscriptionActivePlanDetailsQuery {
activeSubscription {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@ export const SUBSCRIPTION_CANCEL_MUTATION = gql(/* GraphQL */ `
}
`);

gql(/* GraphQL */ `
fragment subscriptionActiveSubscriptionFragment on SubscriptionScheduleType {
phases {
startDate
endDate
trialEnd
item {
price {
...subscriptionPlanItemFragment
}
quantity
}
}
subscription {
startDate
trialEnd
trialStart
}
canActivateTrial
defaultPaymentMethod {
...stripePaymentMethodFragment
}
}
`);
// gql(/* GraphQL */ `
// fragment subscriptionActiveSubscriptionFragment on SubscriptionScheduleType {
// phases {
// startDate
// endDate
// trialEnd
// item {
// price {
// ...subscriptionPlanItemFragment
// }
// quantity
// }
// }
// subscription {
// startDate
// trialEnd
// trialStart
// }
// canActivateTrial
// defaultPaymentMethod {
// ...stripePaymentMethodFragment
// }
// }
// `);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { OperationDescriptor } from 'react-relay/hooks';
import { Route, Routes } from 'react-router-dom';
import { MockPayloadGenerator, RelayMockEnvironment } from 'relay-test-utils';

import { fillSubscriptionScheduleQuery, subscriptionPlanFactory } from '../../../../mocks/factories';
import {
fillSubscriptionPlansAllQuery,
fillSubscriptionScheduleQuery,
subscriptionPlanFactory,
} from '../../../../mocks/factories';
import { snackbarActions } from '../../../../modules/snackbar';
import subscriptionPlansAllQueryGraphql from '../../../../modules/subscription/__generated__/subscriptionPlansAllQuery.graphql';
import { SubscriptionPlanName } from '../../../../shared/services/api/subscription/types';
Expand Down Expand Up @@ -92,13 +96,14 @@ describe('EditSubscription: Component', () => {
it('should show success message and redirect to my subscription page', async () => {
const relayEnvironment = getRelayEnv();
const requestMock = fillCurrentSubscriptionQuery(relayEnvironment);
const requestPlansMock = fillSubscriptionPlansAllQuery(relayEnvironment, [mockMonthlyPlan]);
const requestMockMutation = fillChangeSubscriptionMutation();

const routerProps = createMockRouterProps(['home']);
render(<Component />, {
relayEnvironment,
routerProps,
apolloMocks: (defaultMock) => defaultMock.concat(requestMock, requestMockMutation),
apolloMocks: (defaultMock) => defaultMock.concat(requestMock, requestMockMutation, requestPlansMock),
});

await userEvent.click(await screen.findByText(/monthly/i));
Expand All @@ -123,13 +128,14 @@ describe('EditSubscription: Component', () => {

const requestMock = fillCurrentSubscriptionQuery(relayEnvironment);
const errorMessage = 'Missing payment method';
const requestPlansMock = fillSubscriptionPlansAllQuery(relayEnvironment, [mockMonthlyPlan]);
const requestMockMutation = fillChangeSubscriptionMutation([new GraphQLError(errorMessage)]);

const routerProps = createMockRouterProps(['home']);
render(<Component />, {
relayEnvironment,
routerProps,
apolloMocks: (defaultMock) => defaultMock.concat(requestMock, requestMockMutation),
apolloMocks: (defaultMock) => defaultMock.concat(requestMock, requestMockMutation, requestPlansMock),
});

await userEvent.click(await screen.findByText(/monthly/i));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
import { useQuery } from '@apollo/client';
import { screen, waitFor } from '@testing-library/react';
import { useLazyLoadQuery } from 'react-relay';
import { MockPayloadGenerator } from 'relay-test-utils';
import { OperationDescriptor } from 'react-relay/hooks';
import userEvent from '@testing-library/user-event';
import { Route, Routes } from 'react-router-dom';
import { append } from 'ramda';
import { OperationDescriptor } from 'react-relay/hooks';
import { Route, Routes } from 'react-router-dom';
import { MockPayloadGenerator } from 'relay-test-utils';

import { SubscriptionPlanItem, SubscriptionPlanItemProps } from '../subscriptionPlanItem.component';
import {
fillSubscriptionPlansAllQuery,
fillSubscriptionScheduleQuery,
subscriptionFactory,
subscriptionPhaseFactory,
subscriptionPlanFactory,
} from '../../../../../mocks/factories';
import subscriptionPlansAllQueryGraphql from '../../../../../modules/subscription/__generated__/subscriptionPlansAllQuery.graphql';
import { SubscriptionPlanName } from '../../../../../shared/services/api/subscription/types';
import { render } from '../../../../../tests/utils/rendering';
import subscriptionPlansAllQueryGraphql, {
subscriptionPlansAllQuery,
} from '../../../../../modules/subscription/__generated__/subscriptionPlansAllQuery.graphql';
import { mapConnection } from '../../../../../shared/utils/graphql';
import { useActiveSubscriptionDetails } from '../../../activeSubscriptionContext/activeSubscriptionContext.hooks';
import { ActiveSubscriptionContext } from '../../../activeSubscriptionContext/activeSubscriptionContext.component';
import { getRelayEnv as getBaseRelayEnv } from '../../../../../tests/utils/relay';
import { connectionFromArray } from '../../../../../tests/utils/fixtures';
import { getRelayEnv as getBaseRelayEnv } from '../../../../../tests/utils/relay';
import { render } from '../../../../../tests/utils/rendering';
import { ActiveSubscriptionContext } from '../../../activeSubscriptionContext/activeSubscriptionContext.component';
import { useActiveSubscriptionDetails } from '../../../activeSubscriptionContext/activeSubscriptionContext.hooks';
import { SUBSCRIPTION_PLANS_ALL_QUERY } from '../../subscriptionPlans/subscriptionPlans.graphql';
import { SubscriptionPlanItem, SubscriptionPlanItemProps } from '../subscriptionPlanItem.component';

describe('SubscriptionPlanItem: Component', () => {
const defaultProps: Pick<SubscriptionPlanItemProps, 'onSelect'> = { onSelect: () => jest.fn() };

const Component = (props: Partial<SubscriptionPlanItemProps>) => {
const data = useLazyLoadQuery<subscriptionPlansAllQuery>(subscriptionPlansAllQueryGraphql, {});
const { activeSubscription } = useActiveSubscriptionDetails();
const { data } = useQuery(SUBSCRIPTION_PLANS_ALL_QUERY);

const plans = mapConnection((plan) => plan, data.allSubscriptionPlans);
const plans = mapConnection((plan) => plan, data?.allSubscriptionPlans);

return (
<SubscriptionPlanItem {...defaultProps} plan={plans[0]} activeSubscription={activeSubscription} {...props} />
<SubscriptionPlanItem
{...defaultProps}
plan={plans[0]}
activeSubscription={activeSubscription}
loading={false}
{...props}
/>
);
};

Expand Down Expand Up @@ -67,50 +73,44 @@ describe('SubscriptionPlanItem: Component', () => {
return relayEnvironment;
};

const freePlan = subscriptionPlanFactory({
id: 'plan_free',
product: { name: SubscriptionPlanName.FREE },
});

const monthlyPlan = subscriptionPlanFactory({
id: 'plan_monthly',
pk: 'price_monthly',
product: { name: SubscriptionPlanName.MONTHLY },
});

const yearlyPlan = subscriptionPlanFactory({ id: 'plan_yearly', product: { name: SubscriptionPlanName.YEARLY } });

const subscriptionWithMonthlyPlan = subscriptionFactory({
phases: [subscriptionPhaseFactory({ item: { price: monthlyPlan } })],
});

const subscriptionMigrationToYearly = subscriptionFactory({
phases: [
subscriptionPhaseFactory({ item: { price: monthlyPlan } }),
subscriptionPhaseFactory({ item: { price: yearlyPlan } }),
],
});

it('should render name', async () => {
const relayEnvironment = getRelayEnv();
fillSubscriptionScheduleQuery(relayEnvironment, subscriptionWithMonthlyPlan);
render(<Wrapper />, { relayEnvironment });
const requestPlansMock = fillSubscriptionPlansAllQuery(relayEnvironment, [monthlyPlan]);
render(<Wrapper />, {
relayEnvironment,
apolloMocks: append(requestPlansMock),
});

expect(await screen.findByText(/monthly/i)).toBeInTheDocument();
});

it('should render plan price', async () => {
const relayEnvironment = getRelayEnv();
fillSubscriptionScheduleQuery(relayEnvironment, subscriptionWithMonthlyPlan);
render(<Wrapper />, { relayEnvironment });
expect(await screen.findByText(/2\.5 USD/i)).toBeInTheDocument();
const requestPlansMock = fillSubscriptionPlansAllQuery(relayEnvironment, [monthlyPlan]);
render(<Wrapper />, { relayEnvironment, apolloMocks: append(requestPlansMock) });
expect(await screen.findByText(/10 USD/i)).toBeInTheDocument();
});

describe('button is clicked', () => {
describe('next billing plan is different from the clicked one', () => {
it('should call onSelect', async () => {
const onSelect = jest.fn();
const relayEnvironment = getRelayEnv();
fillSubscriptionScheduleQuery(relayEnvironment, subscriptionMigrationToYearly);
const { waitForApolloMocks } = render(<Wrapper onSelect={onSelect} />, { relayEnvironment });
const requestPlansMock = fillSubscriptionPlansAllQuery(relayEnvironment, [monthlyPlan]);
const { waitForApolloMocks } = render(<Wrapper onSelect={onSelect} />, {
relayEnvironment,
apolloMocks: append(requestPlansMock),
});
await waitForApolloMocks();
await userEvent.click(screen.getByText(/select/i));
await waitFor(() => {
Expand All @@ -124,10 +124,11 @@ describe('SubscriptionPlanItem: Component', () => {
const onSelect = jest.fn();
const relayEnvironment = getRelayEnv();
const requestMock = fillSubscriptionScheduleQuery(relayEnvironment, subscriptionWithMonthlyPlan);
const requestPlansMock = fillSubscriptionPlansAllQuery(relayEnvironment, [monthlyPlan]);

const { waitForApolloMocks } = render(<Wrapper onSelect={onSelect} />, {
relayEnvironment,
apolloMocks: append(requestMock),
apolloMocks: (defaultMocks) => defaultMocks.concat(requestMock, requestPlansMock),
});
await waitForApolloMocks();

Expand All @@ -140,16 +141,11 @@ describe('SubscriptionPlanItem: Component', () => {
it('should call onSelect', async () => {
const onSelect = jest.fn();
const relayEnvironment = getRelayEnv();
fillSubscriptionScheduleQuery(
const requestPlansMock = fillSubscriptionPlansAllQuery(relayEnvironment, [monthlyPlan]);
const { waitForApolloMocks } = render(<Wrapper onSelect={onSelect} />, {
relayEnvironment,
subscriptionFactory({
phases: [
subscriptionPhaseFactory({ item: { price: monthlyPlan } }),
subscriptionPhaseFactory({ item: { price: freePlan } }),
],
})
);
const { waitForApolloMocks } = render(<Wrapper onSelect={onSelect} />, { relayEnvironment });
apolloMocks: append(requestPlansMock),
});
await waitForApolloMocks();
await userEvent.click(screen.getByText(/select/i));
expect(onSelect).toHaveBeenCalled();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { FormattedMessage } from 'react-intl';
import { useFragment } from 'react-relay';

import subscriptionPlanItemFragmentGraphql, {
subscriptionPlanItemFragment$key,
} from '../../../../modules/subscription/__generated__/subscriptionPlanItemFragment.graphql';
import { useActiveSubscriptionDetailsData } from '../../../../shared/hooks/finances/useActiveSubscriptionDetailsData/useActiveSubscriptionDetailsData';
import { useSubscriptionPlanDetails } from '../../../../shared/hooks/finances/useSubscriptionPlanDetails';
import { FragmentType, useFragment } from '../../../../shared/services/graphqlApi/__generated/gql';
import { StripeSubscriptionQueryQuery } from '../../../../shared/services/graphqlApi/__generated/gql/graphql';
import { SUBSRIPTION_PLAN_ITEM_FRAGMENT } from '../subscriptionPlans/subscriptionPlans.graphql';
import { Container, Content, Feature, FeaturesList, Name, SelectButton } from './subscriptionPlanItem.styles';

export type SubscriptionPlanItemProps = {
plan: subscriptionPlanItemFragment$key;
onSelect: (id: string | null) => void;
plan: FragmentType<typeof SUBSRIPTION_PLAN_ITEM_FRAGMENT>;
onSelect: (id: string | null | undefined) => void;
className?: string;
activeSubscription: StripeSubscriptionQueryQuery['activeSubscription'];
loading: boolean;
Expand All @@ -24,13 +22,15 @@ export const SubscriptionPlanItem = ({
activeSubscription,
loading,
}: SubscriptionPlanItemProps) => {
const data = useFragment<subscriptionPlanItemFragment$key>(subscriptionPlanItemFragmentGraphql, plan);
const data = useFragment(SUBSRIPTION_PLAN_ITEM_FRAGMENT, plan);
const { name, price, features, isFree } = useSubscriptionPlanDetails(data);
const { isTrialEligible, activeSubscriptionIsCancelled, activeSubscriptionPlan, nextSubscriptionPlanDetails } =
useActiveSubscriptionDetailsData(activeSubscription);
const isActive = activeSubscriptionPlan.name === name && !activeSubscriptionIsCancelled;
const isScheduledForNextPeriod = nextSubscriptionPlanDetails.name === name;

const handleSelect = () => onSelect(data.pk);

return (
<Container isActive={isActive} className={className}>
<Content>
Expand All @@ -51,7 +51,7 @@ export const SubscriptionPlanItem = ({
</FeaturesList>
</Content>

<SelectButton onClick={() => onSelect(data.pk)} disabled={isScheduledForNextPeriod || isFree || loading}>
<SelectButton onClick={handleSelect} disabled={isScheduledForNextPeriod || isFree || loading}>
<FormattedMessage
defaultMessage="Select ({price} USD)"
id="Change plan item / Select button"
Expand Down
Loading

0 comments on commit 0892159

Please sign in to comment.