Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(commerce): Add method for generating ISBN-10 and ISBN-13 #2240

Merged
merged 20 commits into from
Sep 10, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions src/modules/commerce/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,78 @@
import type { Faker } from '../../faker';
import { deprecated } from '../../internal/deprecated';

// Source for official prefixes: https://www.isbn-international.org/range_file_generation
const ISBN_LENGTH_RULES: {
[group: string]: Array<[rangeMaximum: number, length: number]>;
} = {
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved
'0': [
[1999999, 2],
[2279999, 3],
[2289999, 4],
[3689999, 3],
[3699999, 4],
[6389999, 3],
[6397999, 4],
[6399999, 7],
[6449999, 3],
[6459999, 7],
[6479999, 3],
[6489999, 7],
[6549999, 3],
[6559999, 4],
[6999999, 3],
[8499999, 4],
[8999999, 5],
[9499999, 6],
[9999999, 7],
],
'1': [
[99999, 3],
[299999, 2],
[349999, 3],
[399999, 4],
[499999, 3],
[699999, 2],
[999999, 4],
[3979999, 3],
[5499999, 4],
[6499999, 5],
[6799999, 4],
[6859999, 5],
[7139999, 4],
[7169999, 3],
[7319999, 4],
[7399999, 7],
[7749999, 5],
[7753999, 7],
[7763999, 5],
[7764999, 7],
[7769999, 5],
[7782999, 7],
[7899999, 5],
[7999999, 4],
[8004999, 5],
[8049999, 5],
[8379999, 5],
[8384999, 7],
[8671999, 5],
[8675999, 4],
[8697999, 5],
[9159999, 6],
[9165059, 7],
[9168699, 6],
[9169079, 7],
[9195999, 6],
[9196549, 7],
[9729999, 6],
[9877999, 4],
[9911499, 6],
[9911999, 7],
[9989899, 6],
[9999999, 7],
],
};

/**
* Module to generate commerce and product related entries.
*
Expand Down Expand Up @@ -266,4 +338,84 @@ export class CommerceModule {
this.faker.definitions.commerce.product_description
);
}

/**
* Returns a random [ISBN](https://en.wikipedia.org/wiki/ISBN) identifier.
*
* @param options The variant to return or an options object. Defaults to `{}`.
* @param options.variant The variant to return. Can be either `10` (10-digit format)
* or `13` (13-digit format). Defaults to `13`.
* @param options.separator The separator to use in the format. Defaults to `'-'`.
*
* @example
* faker.commerce.isbn() // '978-0-692-82459-7'
* faker.commerce.isbn(10) // '1-155-36404-X'
* faker.commerce.isbn(13) // '978-1-60808-867-6'
* faker.commerce.isbn({ separator: ' ' }) // '978 0 452 81498 1'
* faker.commerce.isbn({ variant: 10, separator: ' ' }) // '0 940319 49 7'
* faker.commerce.isbn({ variant: 13, separator: ' ' }) // '978 1 6618 9122 0'
*
* @since 8.0.0
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved
*/
isbn(
options:
| 10
| 13
| {
/**
* The variant of the identifier to return.
* Can be either `10` (10-digit format)
* or `13` (13-digit format).
*
* @default 13
*/
variant?: 10 | 13;

/**
* The separator to use in the format.
*
* @default '-'
*/
separator?: string;
} = {}
): string {
if (typeof options === 'number') {
options = { variant: options };
}

const { variant = 13, separator = '-' } = options;

const prefix = '978';
const group: string = this.faker.helpers
.objectKey(ISBN_LENGTH_RULES)
.toString();
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved
const element: string = this.faker.string.numeric(8);
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved

const registrantLength: number = ISBN_LENGTH_RULES[group].find(
([rangeMaximum]): boolean =>
parseInt(element.slice(0, -1)) <= rangeMaximum
)[1];
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved

const registrant: string = element.slice(0, registrantLength);
const publication: string = element.slice(registrantLength);
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved

const data: string[] = [prefix, group, registrant, publication];
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved
if (variant === 10) {
data.shift();
}

const isbn: string = data.join('');
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved

let checksum = 0;
for (let i = 0; i < variant - 1; i++) {
const weight = variant === 10 ? i + 1 : i % 2 ? 3 : 1;
checksum += weight * parseInt(isbn[i]);
}

checksum = variant === 10 ? checksum % 11 : (10 - (checksum % 10)) % 10;

data.push(checksum === 10 ? 'X' : checksum.toString());

return data.join(separator);
}
}
30 changes: 30 additions & 0 deletions test/__snapshots__/commerce.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

exports[`commerce > 42 > department 1`] = `"Tools"`;

exports[`commerce > 42 > isbn > noArgs 1`] = `"978-0-7917-7551-6"`;

exports[`commerce > 42 > isbn > with space separators 1`] = `"978 0 7917 7551 6"`;

exports[`commerce > 42 > isbn > with variant 10 1`] = `"0-7917-7551-8"`;

exports[`commerce > 42 > isbn > with variant 10 and space separators 1`] = `"0 7917 7551 8"`;

exports[`commerce > 42 > isbn > with variant 13 1`] = `"978-0-7917-7551-6"`;

exports[`commerce > 42 > price > noArgs 1`] = `"375.00"`;

exports[`commerce > 42 > price > with max 1`] = `"375.00"`;
Expand Down Expand Up @@ -36,6 +46,16 @@ exports[`commerce > 42 > productName 1`] = `"Fantastic Soft Sausages"`;

exports[`commerce > 1211 > department 1`] = `"Automotive"`;

exports[`commerce > 1211 > isbn > noArgs 1`] = `"978-1-4872-1906-2"`;

exports[`commerce > 1211 > isbn > with space separators 1`] = `"978 1 4872 1906 2"`;

exports[`commerce > 1211 > isbn > with variant 10 1`] = `"1-4872-1906-7"`;

exports[`commerce > 1211 > isbn > with variant 10 and space separators 1`] = `"1 4872 1906 7"`;

exports[`commerce > 1211 > isbn > with variant 13 1`] = `"978-1-4872-1906-2"`;

exports[`commerce > 1211 > price > noArgs 1`] = `"929.00"`;

exports[`commerce > 1211 > price > with max 1`] = `"929.00"`;
Expand Down Expand Up @@ -70,6 +90,16 @@ exports[`commerce > 1211 > productName 1`] = `"Unbranded Cotton Salad"`;

exports[`commerce > 1337 > department 1`] = `"Computers"`;

exports[`commerce > 1337 > isbn > noArgs 1`] = `"978-0-512-25403-0"`;

exports[`commerce > 1337 > isbn > with space separators 1`] = `"978 0 512 25403 0"`;

exports[`commerce > 1337 > isbn > with variant 10 1`] = `"0-512-25403-6"`;

exports[`commerce > 1337 > isbn > with variant 10 and space separators 1`] = `"0 512 25403 6"`;

exports[`commerce > 1337 > isbn > with variant 13 1`] = `"978-0-512-25403-0"`;

exports[`commerce > 1337 > price > noArgs 1`] = `"263.00"`;

exports[`commerce > 1337 > price > with max 1`] = `"263.00"`;
Expand Down
66 changes: 66 additions & 0 deletions test/commerce.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import validator from 'validator';
import { describe, expect, it } from 'vitest';
import { faker } from '../src';
import { seededTests } from './support/seededRuns';
Expand Down Expand Up @@ -38,6 +39,17 @@ describe('commerce', () => {
symbol: '$',
});
});

t.describe('isbn', (t) => {
t.it('noArgs')
.it('with variant 10', 10)
.it('with variant 13', 13)
.it('with variant 10 and space separators', {
variant: 10,
separator: ' ',
})
.it('with space separators', { separator: ' ' });
});
});

describe.each(times(NON_SEEDED_BASED_RUN).map(() => faker.seed()))(
Expand Down Expand Up @@ -158,6 +170,60 @@ describe('commerce', () => {
);
});
});

describe(`isbn()`, () => {
it('should return ISBN-13 with hyphen separators when not passing arguments', () => {
RobinvanderVliet marked this conversation as resolved.
Show resolved Hide resolved
const isbn = faker.commerce.isbn();

expect(isbn).toBeTruthy();
expect(isbn).toBeTypeOf('string');
expect(
isbn,
'The expected match should be ISBN-13 with hyphens'
).toMatch(/^978-[01]-[\d-]{9}-\d$/);
expect(isbn).toSatisfy((isbn: string) => validator.isISBN(isbn, 13));
});

it('should return ISBN-10 with hyphen separators when passing variant 10 as argument', () => {
const isbn = faker.commerce.isbn(10);

expect(
isbn,
'The expected match should be ISBN-10 with hyphens'
).toMatch(/^[01]-[\d-]{9}-[\dX]$/);
expect(isbn).toSatisfy((isbn: string) => validator.isISBN(isbn, 10));
});

it('should return ISBN-13 with hyphen separators when passing variant 13 as argument', () => {
const isbn = faker.commerce.isbn(13);

expect(
isbn,
'The expected match should be ISBN-13 with hyphens'
).toMatch(/^978-[01]-[\d-]{9}-\d$/);
expect(isbn).toSatisfy((isbn: string) => validator.isISBN(isbn, 13));
});

it('should return ISBN-10 with space separators when passing variant 10 and space separators as argument', () => {
const isbn = faker.commerce.isbn({ variant: 10, separator: ' ' });

expect(
isbn,
'The expected match should be ISBN-10 with space separators'
).toMatch(/^[01] [\d ]{9} [\dX]$/);
expect(isbn).toSatisfy((isbn: string) => validator.isISBN(isbn, 10));
});

it('should return ISBN-13 with space separators when passing space separators as argument', () => {
const isbn = faker.commerce.isbn({ separator: ' ' });

expect(
isbn,
'The expected match should be ISBN-13 with space separators'
).toMatch(/^978 [01] [\d ]{9} \d$/);
expect(isbn).toSatisfy((isbn: string) => validator.isISBN(isbn, 13));
});
});
}
);
});