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 16 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
Expand Up @@ -2,6 +2,79 @@ import type { Faker } from '../../faker';
import { bindThisToMemberFunctions } from '../../internal/bind-this-to-member-functions';
import { deprecated } from '../../internal/deprecated';

// Source for official prefixes: https://www.isbn-international.org/range_file_generation
const ISBN_LENGTH_RULES: Record<
string,
Array<[rangeMaximum: number, length: number]>
> = {
'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 @@ -258,4 +331,83 @@ 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.1.0
*/
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, groupRules] =
this.faker.helpers.objectEntry(ISBN_LENGTH_RULES);
const element = this.faker.string.numeric(8);
const elementValue = parseInt(element.slice(0, -1));

const registrantLength = groupRules.find(
([rangeMaximum]) => elementValue <= rangeMaximum
)[1];

const registrant = element.slice(0, registrantLength);
const publication = element.slice(registrantLength);

const data = [prefix, group, registrant, publication];
if (variant === 10) {
data.shift();
}

const isbn = data.join('');

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));
});
});
}
);
});