Skip to content

Commit

Permalink
feat: strict mode (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
bennypowers committed Aug 22, 2022
1 parent 0288e78 commit 3b989dd
Show file tree
Hide file tree
Showing 19 changed files with 287 additions and 290 deletions.
5 changes: 3 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
},
"overrides": [
{
"files": "**/*.test.ts",
"files": ["**/*.test.ts", "test/test-helpers.ts"],
"rules": {
"max-len": 0
"max-len": 0,
"@typescript-eslint/no-non-null-assertion": 0
}
},
{
Expand Down
81 changes: 35 additions & 46 deletions src/StripeBase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TemplateResult, PropertyValues } from 'lit';
import type { TemplateResult, PropertyValues, ComplexAttributeConverter } from 'lit';
import type * as Stripe from '@stripe/stripe-js';
import { LitElement, html } from 'lit';
import { property } from 'lit/decorators.js';
Expand All @@ -22,16 +22,15 @@ export const enum SlotName {
}

export type PaymentRepresentation = 'payment-method'|'source'|'token'

export type StripePaymentResponse =
| Stripe.PaymentIntentResult
| Stripe.PaymentMethodResult
| Stripe.SetupIntentResult
| Stripe.TokenResult
| Stripe.SourceResult

type AmbiguousError =
Error|Stripe.StripeError|StripeElementsError;
type StripeElementType = Stripe.StripeCardElement | Stripe.StripePaymentRequestButtonElement;
type AmbiguousError = Error|Stripe.StripeError|StripeElementsError;

declare global {
interface Node {
Expand All @@ -48,12 +47,12 @@ class StripeElementsError extends Error {
}
}

function isStripeElementsError(error: AmbiguousError): error is StripeElementsError {
function isStripeElementsError(error: AmbiguousError | null): error is StripeElementsError {
return !!error && error instanceof StripeElementsError;
}

const errorConverter = {
toAttribute: (error: AmbiguousError): string =>
const errorConverter: ComplexAttributeConverter = {
toAttribute: (error: AmbiguousError) =>
!error ? null
: isStripeElementsError(error) ? error.originalMessage
: error.message || '',
Expand All @@ -71,29 +70,29 @@ const errorConverter = {
* @csspart 'stripe' - container for the stripe element
*/
export class StripeBase extends LitElement {
static is: 'stripe-elements'|'stripe-payment-request'
static is: 'stripe-elements'|'stripe-payment-request';

/* PAYMENT CONFIGURATION */

/**
* billing_details object sent to create the payment representation. (optional)
*/
billingDetails: Stripe.PaymentMethod.BillingDetails;
billingDetails?: Stripe.CreatePaymentMethodData['billing_details'];

/**
* Data passed to stripe.createPaymentMethod. (optional)
*/
paymentMethodData: Stripe.CreatePaymentMethodData;
paymentMethodData?: Stripe.CreatePaymentMethodData;

/**
* Data passed to stripe.createSource. (optional)
*/
sourceData: Stripe.CreateSourceData;
sourceData?: Stripe.CreateSourceData;

/**
* Data passed to stripe.createToken. (optional)
*/
tokenData: Stripe.CreateTokenCardData;
tokenData?: Stripe.CreateTokenCardData;

/* SETTINGS */

Expand All @@ -114,39 +113,32 @@ export class StripeBase extends LitElement {
* stripeElements.submit();
* ```
*/
@property({ type: String })
action: string;
@property({ type: String }) action?: string;

/**
* The `client_secret` part of a Stripe `PaymentIntent`
*/
@property({ type: String, attribute: 'client-secret' })
clientSecret: string;
@property({ type: String, attribute: 'client-secret' }) clientSecret?: string;

/**
* Type of payment representation to generate.
*/
@property({ type: String })
generate: PaymentRepresentation = 'source';
@property({ type: String }) generate: PaymentRepresentation = 'source';

/**
* Stripe Publishable Key. EG. `pk_test_XXXXXXXXXXXXXXXXXXXXXXXX`
*/
@notify
@property({ type: String, attribute: 'publishable-key', reflect: true })
publishableKey: string;
@property({ type: String, attribute: 'publishable-key', reflect: true }) publishableKey?: string;

/** Whether to display the error message */
@property({ type: Boolean, attribute: 'show-error', reflect: true })
showError = false;
@property({ type: Boolean, attribute: 'show-error', reflect: true }) showError = false;

/** Stripe account to use (connect) */
@property({ type: String, attribute: 'stripe-account' })
stripeAccount: string;
@property({ type: String, attribute: 'stripe-account' }) stripeAccount?: string;

/** Stripe locale to use */
@property({ type: String, attribute: 'locale' })
locale: StripeElementLocale = 'auto';
@property({ type: String, attribute: 'locale' }) locale: StripeElementLocale = 'auto';

/* READ-ONLY FIELDS */

Expand All @@ -158,45 +150,45 @@ export class StripeBase extends LitElement {
@readonly
@notify
@property({ type: Object, attribute: 'payment-method' })
readonly paymentMethod: Stripe.PaymentMethod = null;
readonly paymentMethod: Stripe.PaymentMethod | null = null;

/**
* Stripe Source
*/
@readonly
@notify
@property({ type: Object })
readonly source: Stripe.Source = null;
readonly source: Stripe.Source | null = null;

/**
* Stripe Token
*/
@readonly
@notify
@property({ type: Object })
readonly token: Stripe.Token = null;
readonly token: Stripe.Token | null = null;

/**
* Stripe element instance
*/
@readonly
@property({ type: Object })
readonly element: Stripe.StripeCardElement|Stripe.StripePaymentRequestButtonElement = null;
readonly element: StripeElementType | null = null;

/**
* Stripe Elements instance
*/
@readonly
@property({ type: Object })
readonly elements: Stripe.StripeElements = null;
readonly elements: Stripe.StripeElements | null = null;

/**
* Stripe or validation error
*/
@readonly
@notify
@property({ type: Object, reflect: true, converter: errorConverter })
readonly error: null|AmbiguousError = null;
readonly error: AmbiguousError | null = null;

/**
* If the element is focused.
Expand All @@ -219,7 +211,7 @@ export class StripeBase extends LitElement {
*/
@readonly
@property({ type: Object })
readonly stripe: Stripe.Stripe = null;
readonly stripe: Stripe.Stripe | null = null;

/**
* Stripe appearance theme
Expand Down Expand Up @@ -273,7 +265,7 @@ export class StripeBase extends LitElement {
super.updated?.(changed);
if (changed.has('error')) this.errorChanged();
if (changed.has('publishableKey')) this.init();
[...changed.keys()].forEach(this.representationChanged);
[...changed.keys()].forEach(k => this.representationChanged(k));
}

/** @inheritdoc */
Expand Down Expand Up @@ -337,7 +329,7 @@ export class StripeBase extends LitElement {
/**
* Fires an Error Event
*/
private fireError(error: AmbiguousError): void {
private fireError(error: AmbiguousError | null): void {
this.dispatchEvent(new ErrorEvent('error', { error }));
}

Expand Down Expand Up @@ -374,7 +366,7 @@ export class StripeBase extends LitElement {
private async init(): Promise<void> {
await this.unmount();
await this.initStripe();
await this.initElement();
await this.initElement!();
this.initElementListeners();
this.breadcrumb.init();
this.mount();
Expand Down Expand Up @@ -404,7 +396,8 @@ export class StripeBase extends LitElement {
try {
const options = { stripeAccount, locale };
const stripe =
(window.Stripe) ? window.Stripe(publishableKey, options) : await loadStripe(publishableKey, options);
(window.Stripe) ? window.Stripe(publishableKey, options)
: await loadStripe(publishableKey, options);
const elements = stripe?.elements();
readonly.set<StripeBase>(this, { elements, error: null, stripe });
} catch (e) {
Expand Down Expand Up @@ -445,10 +438,6 @@ export class StripeBase extends LitElement {
await this.updateComplete;
}

/**
* @param {StripeFocusEvent} event
* @private
*/
@bound private async onFocus(): Promise<void> {
readonly.set<StripeBase>(this, { focused: true });
await this.updateComplete;
Expand All @@ -475,7 +464,7 @@ export class StripeBase extends LitElement {
const body = JSON.stringify({ token, source, paymentMethod });
const headers = { 'Content-Type': 'application/json' };
const method = 'POST';
return fetch(this.action, { body, headers, method })
return fetch(this.action!, { body, headers, method })
.then(throwBadResponse)
.then(onSuccess)
.catch(onError);
Expand All @@ -484,14 +473,14 @@ export class StripeBase extends LitElement {
/**
* Updates the state and fires events when the token, source, or payment method is updated.
*/
@bound private representationChanged(name: string): void {
if (!isRepresentation(name))
private representationChanged(name: PropertyKey): void {
if (!isRepresentation(name as string))
return;
const value = this[name];
const value = this[name as keyof this];
/* istanbul ignore if */
if (!value)
return;
this.fire(`${dash(name)}`, value);
this.fire(`${dash(name as string)}`, value);
if (this.action)
this.postRepresentation();
}
Expand Down
20 changes: 10 additions & 10 deletions src/breadcrumb-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@ export class BreadcrumbController implements ReactiveController {
/**
* Mount point element. This element must be connected to the document.
*/
public get mount(): Element { return document.getElementById(this.mountId); }
public get mount(): Element|null { return document.getElementById(this.mountId); }

constructor(
private host: ReactiveControllerHost & Element,
private options?: BreadcrumbControllerOptions
) {
this.host.addController(this);
this.resetMountId();
this.mountId = this.resetMountId();
this.slotName = this.options?.slotName ?? `breadcrumb-${getRandom()}`;
}

hostUpdated(): void {
if (!this.initialized && this.options.autoInitialize !== false)
if (!this.initialized && this.options?.autoInitialize !== false)
this.init();
}

Expand All @@ -52,8 +52,8 @@ export class BreadcrumbController implements ReactiveController {
}

private resetMountId() {
const prefix = this.options.mountPrefix ?? this.host.localName;
this.mountId = `${prefix}-mount-point-${getRandom()}`;
const prefix = this.options?.mountPrefix ?? this.host.localName;
return `${prefix}-mount-point-${getRandom()}`;
}

private createMountPoint(): HTMLElement {
Expand Down Expand Up @@ -93,7 +93,7 @@ export class BreadcrumbController implements ReactiveController {
}
if (this.mount)
this.mount.remove();
this.resetMountId();
this.mountId = this.resetMountId();
}

/**
Expand All @@ -111,16 +111,16 @@ export class BreadcrumbController implements ReactiveController {
// Prepare the shallowest breadcrumb slot at document level
const hosts = [...shadowHosts];
const root = hosts.pop();
if (!root.querySelector(`[slot="${slotName}"]`)) {
if (!root!.querySelector(`[slot="${slotName}"]`)) {
const div = document.createElement('div');
div.slot = slotName;
root.appendChild(div);
root!.appendChild(div);
}

const container = root.querySelector(`[slot="${slotName}"]`);
const container = root!.querySelector(`[slot="${slotName}"]`);

// Render the form to the document, so that the slotted content can mount
this.appendTemplate(container);
this.appendTemplate(container!);

// Append breadcrumb slots to each shadowroot in turn,
// from the document down to the <stripe-elements> instance.
Expand Down
2 changes: 1 addition & 1 deletion src/lib/functions.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const unary = f => x => f(x);
export const unary = (f: (...args:any[]) => unknown) => (x: unknown) => f(x);
7 changes: 4 additions & 3 deletions src/lib/notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ class NotifyController implements ReactiveController {

constructor(private host: HTMLElement & ReactiveControllerHost) {
if (NotifyController.instances.has(host))
return NotifyController.instances.get(host);
return NotifyController.instances.get(host) as this;
host.addController(this);
NotifyController.instances.set(host, this);
}

hostUpdated() {
// eslint-disable-next-line easy-loops/easy-loops
for (const [key, oldValue] of this.cache) {
const newValue = this.host[key];
const newValue = this.host[key as keyof typeof this.host];
const { attribute } = (this.host.constructor as typeof ReactiveElement)
.getPropertyOptions(key) ?? {};
const attr = typeof attribute === 'string' ? attribute : null;
Expand All @@ -42,6 +43,6 @@ class NotifyController implements ReactiveController {
export function notify<T extends ReactiveElement>(proto: T, key: string) {
(proto.constructor as typeof ReactiveElement).addInitializer(x => {
const controller = new NotifyController(x);
controller.cache.set(key, x[key]);
controller.cache.set(key, x[key as keyof typeof x]);
});
}
2 changes: 1 addition & 1 deletion src/lib/object.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const objectOf = key => x => ({ [key]: x });
export const objectOf = <T extends object>(key: keyof T) => (x: T[keyof T]) => ({ [key]: x });
4 changes: 2 additions & 2 deletions src/lib/predicates.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const elem = xs => x => xs.includes(x);
export const elem = <T>(xs: readonly T[]) => (x: T) => xs.includes(x);

export const not = p => x => !p(x);
export const not = <T>(p: (x: T) => boolean) => (x: T) => !p(x);

export const isRepresentation = elem(['paymentMethod', 'source', 'token']);
2 changes: 1 addition & 1 deletion src/lib/read-only.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ReadOnlyController implements ReactiveController {

constructor(private host: ReactiveControllerHost) {
if (ReadOnlyController.instances.has(host))
return ReadOnlyController.instances.get(host);
return ReadOnlyController.instances.get(host) as this;
host.addController(this);
ReadOnlyController.instances.set(host, this);
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import eagerDash from '@lavadrop/kebab-case';
import eagerCamel from '@lavadrop/camel-case';
import { memoize } from '@pacote/memoize';

const identity = x => x;
const identity = <T>(x: T): T => x;

/** camelCase a string */
export const camel = memoize(identity, eagerCamel);
Expand Down

0 comments on commit 3b989dd

Please sign in to comment.