Skip to content

Commit

Permalink
feat(commerce): add method for generating ISBN-10 and ISBN-13 (#2240)
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinvanderVliet committed Sep 10, 2023
1 parent 8a6ce49 commit cb4ef28
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 0 deletions.
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/modules/__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/modules/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', () => {
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));
});
});
}
);
});

0 comments on commit cb4ef28

Please sign in to comment.