jsonresume-theme-engineering 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Makefile CHANGED
@@ -2,7 +2,7 @@ HBS_TEMPLATES = $(sort $(wildcard partials/** views/** resume.hbs ))
2
2
 
3
3
  all: resume.png
4
4
 
5
- resume.json:
5
+ resume.json: sample-resume.json
6
6
  cp sample-resume.json resume.json
7
7
 
8
8
  resume.pdf: resume.json index.js $(HBS_TEMPLATES)
@@ -10,3 +10,7 @@ resume.pdf: resume.json index.js $(HBS_TEMPLATES)
10
10
 
11
11
  resume.png: resume.pdf
12
12
  pdftoppm -png resume.pdf > resume.png
13
+
14
+ .PHONY: test
15
+ test:
16
+ npm run test
package/index.js CHANGED
@@ -1,23 +1,20 @@
1
1
  const
2
2
  fs = require('fs'),
3
- handlebars = require('handlebars'),
4
- handlebarsWax = require('handlebars-wax'),
3
+ path = require('path'),
4
+ Handlebars = require('handlebars'),
5
5
  addressFormat = require('address-format'),
6
- moment = require('moment'),
7
- Swag = require('swag');
6
+ moment = require('moment');
8
7
 
9
- Swag.registerHelpers(handlebars);
10
-
11
- handlebars.registerHelper({
8
+ Handlebars.registerHelper({
12
9
 
13
10
  wrapURL: function (url) {
14
11
  const wrappedUrl = '<a href="' + url + '">' + url.replace(/.*?:\/\//g, '') + "</a>";
15
- return new handlebars.SafeString(wrappedUrl);
12
+ return new Handlebars.SafeString(wrappedUrl);
16
13
  },
17
14
 
18
15
  wrapMail: function (address) {
19
16
  const wrappedAddress = '<a href="mailto:' + address + '">' + address + "</a>";
20
- return new handlebars.SafeString(wrappedAddress);
17
+ return new Handlebars.SafeString(wrappedAddress);
21
18
  },
22
19
 
23
20
  formatAddress: function (address, city, region, postalCode, countryCode) {
@@ -42,14 +39,49 @@ handlebars.registerHelper({
42
39
  });
43
40
 
44
41
  function render(resume) {
42
+ if (!resume || typeof resume !== 'object') {
43
+ throw new Error('Expected input to be a valid resume object');
44
+ }
45
+
45
46
  let dir = __dirname,
46
47
  css = fs.readFileSync(dir + '/style.css', 'utf-8'),
47
- resumeTemplate = fs.readFileSync(dir + '/resume.hbs', 'utf-8');
48
+ resumeTemplate = fs.readFileSync(dir + '/resume.hbs', 'utf-8'),
49
+ partialsDir = path.join(dir, 'partials'),
50
+ viewsDir = path.join(dir, 'views');
51
+
52
+ // Load partials from partialsDir
53
+ let partialFilenames = fs.readdirSync(partialsDir);
54
+ partialFilenames.forEach(function (filename) {
55
+ var matches = /^([^.]+).hbs$/.exec(filename);
56
+ if (!matches) {
57
+ return;
58
+ }
59
+ var name = matches[1];
60
+ var filepath = path.join(partialsDir, filename);
61
+ var template = fs.readFileSync(filepath, 'utf8');
62
+
63
+ Handlebars.registerPartial(name, template);
64
+ });
48
65
 
49
- let Handlebars = handlebarsWax(handlebars);
66
+ // Load partials from viewsDir (if it exists)
67
+ try {
68
+ if (fs.existsSync(viewsDir)) {
69
+ let viewFilenames = fs.readdirSync(viewsDir);
70
+ viewFilenames.forEach(function (filename) {
71
+ var matches = /^([^.]+).hbs$/.exec(filename);
72
+ if (!matches) {
73
+ return;
74
+ }
75
+ var name = matches[1];
76
+ var filepath = path.join(viewsDir, filename);
77
+ var template = fs.readFileSync(filepath, 'utf8');
50
78
 
51
- Handlebars.partials(dir + '/views/**/*.{hbs,js}');
52
- Handlebars.partials(dir + '/partials/**/*.{hbs,js}');
79
+ Handlebars.registerPartial(name, template);
80
+ });
81
+ }
82
+ } catch (err) {
83
+ console.error('Error loading views directory:', err);
84
+ }
53
85
 
54
86
  return Handlebars.compile(resumeTemplate)({
55
87
  css: css,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsonresume-theme-engineering",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "JSON Resume theme for engineers",
5
5
  "keywords": [
6
6
  "jsonresume",
@@ -18,16 +18,21 @@
18
18
  "license": "MIT",
19
19
  "scripts": {
20
20
  "start": "resume serve --theme .",
21
- "export": "resume export --theme . resume.pdf"
21
+ "export": "resume export --theme . resume.pdf",
22
+ "test": "mocha test/*.test.js"
22
23
  },
23
24
  "dependencies": {
24
25
  "address-format": "0.0.3",
25
26
  "handlebars": "^4.7.8",
26
- "handlebars-wax": "^6.1.0",
27
- "moment": "^2.30.1",
28
- "swag": "^0.7.0"
27
+ "moment": "^2.30.1"
29
28
  },
30
29
  "devDependencies": {
31
- "resume-cli": "^3.1.2"
30
+ "looks-same": "^9.0.1",
31
+ "mocha": "^10.0.0",
32
+ "pdf-img-convert": "^2.0.0",
33
+ "pdf-parse": "^1.1.1",
34
+ "pngjs": "^7.0.0",
35
+ "resume-cli": "^3.1.2",
36
+ "sinon": "^21.0.0"
32
37
  }
33
38
  }
package/reference.pdf ADDED
Binary file
package/renovate.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ],
6
+ "vulnerabilityAlerts": {
7
+ "labels": ["security"],
8
+ "automerge": false
9
+ }
10
+ }
package/style.css CHANGED
@@ -2,7 +2,15 @@
2
2
  body {
3
3
  font-family: Georgia, serif;
4
4
  font-size: 11px;
5
- margin: 24px 48px;
5
+ display: flex;
6
+ justify-content: center;
7
+ }
8
+
9
+ #resume {
10
+ max-width: 800px;
11
+ width: 100%;
12
+ padding: 24px 48px;
13
+ box-sizing: border-box;
6
14
  }
7
15
 
8
16
  h1 {
@@ -83,7 +91,7 @@ a {
83
91
  size: portrait;
84
92
  margin: 10mm 25mm;
85
93
  }
86
- .resume {
94
+ #resume {
87
95
  max-width: 100%;
88
96
  border: 0px;
89
97
  background: #fff;
@@ -92,7 +100,7 @@ a {
92
100
  }
93
101
  body,
94
102
  html,
95
- .resume {
103
+ #resume {
96
104
  margin: 0px;
97
105
  padding: 0px;
98
106
  }
@@ -0,0 +1,105 @@
1
+ const assert = require('assert');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { render } = require('../index');
5
+
6
+ describe('Render', function() {
7
+ it('should render a resume with valid input', function() {
8
+ const resume = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'sample-resume.json'), 'utf-8'));
9
+ const output = render(resume);
10
+ assert(output.includes('<title>Richard Hendriks</title>'), 'Output should include the name from the resume');
11
+ });
12
+
13
+ it('should throw an error with invalid input', function() {
14
+ assert.throws(() => render(null), Error, 'Expected input to be a valid resume object');
15
+ });
16
+
17
+ it('should handle an empty resume object', function() {
18
+ const emptyResume = {};
19
+ const output = render(emptyResume);
20
+ assert(output.includes('<title></title>'), 'Output should handle an empty resume object');
21
+ });
22
+
23
+ it('should handle a resume object missing required fields', function() {
24
+ const incompleteResume = {
25
+ basics: {
26
+ name: 'Jane Doe'
27
+ }
28
+ };
29
+ const output = render(incompleteResume);
30
+ assert(output.includes('<title>Jane Doe</title>'), 'Output should handle a resume object missing required fields');
31
+ });
32
+
33
+ it('should throw an error with invalid data type', function() {
34
+ assert.throws(() => render('invalid input'), Error, 'Expected input to be a valid resume object');
35
+ });
36
+
37
+ it('should handle a large resume object', function() {
38
+ const largeResume = {
39
+ basics: {
40
+ name: 'Large Resume',
41
+ label: 'Test',
42
+ email: 'large.resume@example.com',
43
+ phone: '(123) 456-7890',
44
+ website: 'http://example.com',
45
+ summary: 'This is a large resume object.',
46
+ location: {
47
+ address: '123 Main St',
48
+ postalCode: '12345',
49
+ city: 'Anytown',
50
+ countryCode: 'US',
51
+ region: 'CA'
52
+ },
53
+ profiles: []
54
+ },
55
+ work: Array(1000).fill({
56
+ company: 'Large Company',
57
+ position: 'Software Engineer',
58
+ website: 'http://example.com',
59
+ startDate: '2000-01-01',
60
+ summary: 'Worked on various projects.',
61
+ highlights: ['Highlight 1', 'Highlight 2']
62
+ }),
63
+ education: [],
64
+ skills: [],
65
+ awards: [],
66
+ publications: [],
67
+ languages: [],
68
+ interests: [],
69
+ references: []
70
+ };
71
+ const output = render(largeResume);
72
+ assert(output.includes('<title>Large Resume</title>'), 'Output should handle a large resume object');
73
+ });
74
+
75
+ it('should handle special characters in fields', function() {
76
+ const specialCharsResume = {
77
+ basics: {
78
+ name: 'Special & Ch@rs',
79
+ label: 'Test',
80
+ email: 'special.chars@example.com',
81
+ phone: '(123) 456-7890',
82
+ website: 'http://example.com',
83
+ summary: 'This is a summary with special characters: &, <, >, ", \'.',
84
+ location: {
85
+ address: '123 Main St',
86
+ postalCode: '12345',
87
+ city: 'Anytown',
88
+ countryCode: 'US',
89
+ region: 'CA'
90
+ },
91
+ profiles: []
92
+ },
93
+ work: [],
94
+ education: [],
95
+ skills: [],
96
+ awards: [],
97
+ publications: [],
98
+ languages: [],
99
+ interests: [],
100
+ references: []
101
+ };
102
+ const output = render(specialCharsResume);
103
+ assert(output.includes('<title>Special &amp; Ch@rs</title>'), 'Output should handle special characters in fields');
104
+ });
105
+ });
@@ -0,0 +1,73 @@
1
+ const assert = require('assert');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const sinon = require('sinon');
5
+ const childProcess = require('child_process');
6
+ const pdfHelper = require('./utils/pdf-helper');
7
+
8
+ describe('PDF Export', function() {
9
+ let execStub;
10
+
11
+ beforeEach(function() {
12
+ // Backup the original PDF
13
+ pdfHelper.backupReferencePdf();
14
+
15
+ // Create a proper sinon stub for the exec function
16
+ execStub = sinon.stub(childProcess, 'exec');
17
+
18
+ // Configure the stub to simulate successful PDF generation
19
+ execStub.callsFake((command, callback) => {
20
+ if (command === 'npm run export') {
21
+ // Simulate the PDF export process
22
+ fs.writeFileSync(pdfHelper.referencePdfPath, 'PDF content');
23
+ callback(null, 'PDF export successful');
24
+ } else {
25
+ callback(new Error('Unknown command'));
26
+ }
27
+ });
28
+ });
29
+
30
+ afterEach(function() {
31
+ // Restore all sinon stubs
32
+ sinon.restore();
33
+
34
+ // Restore the original PDF
35
+ pdfHelper.restoreReferencePdf();
36
+
37
+ // Clean up test PDF
38
+ pdfHelper.cleanupTestPdf();
39
+ });
40
+
41
+ it('should export a PDF with valid input', function(done) {
42
+ const resume = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'sample-resume.json'), 'utf-8'));
43
+ fs.writeFileSync(path.join(__dirname, '..', 'resume.json'), JSON.stringify(resume));
44
+
45
+ childProcess.exec('npm run export', (error, stdout, stderr) => {
46
+ if (error) {
47
+ return done(error);
48
+ }
49
+
50
+ assert(fs.existsSync(pdfHelper.referencePdfPath), 'PDF file should be generated');
51
+
52
+ // Read the file as a Buffer instead of utf-8 string since PDF is binary
53
+ const pdfExists = fs.existsSync(pdfHelper.referencePdfPath);
54
+ assert(pdfExists, 'PDF file should be generated');
55
+
56
+ done();
57
+ });
58
+ });
59
+
60
+ it('should handle errors during PDF export', function(done) {
61
+ // Restore the original stub and create a new one that simulates an error
62
+ sinon.restore();
63
+ execStub = sinon.stub(childProcess, 'exec');
64
+ execStub.callsFake((command, callback) => {
65
+ callback(new Error('Simulated error during PDF export'));
66
+ });
67
+
68
+ childProcess.exec('npm run export', (error, stdout, stderr) => {
69
+ assert(error, 'An error should be thrown during PDF export');
70
+ done();
71
+ });
72
+ });
73
+ });
@@ -0,0 +1,73 @@
1
+ const assert = require('assert');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const sinon = require('sinon');
5
+ const childProcess = require('child_process');
6
+ const pdfHelper = require('./utils/pdf-helper');
7
+
8
+ describe('PDF Export', function() {
9
+ let execStub;
10
+
11
+ beforeEach(function() {
12
+ // Backup the original PDF
13
+ pdfHelper.backupReferencePdf();
14
+
15
+ // Create a proper sinon stub for the exec function
16
+ execStub = sinon.stub(childProcess, 'exec');
17
+
18
+ // Configure the stub to simulate successful PDF generation
19
+ execStub.callsFake((command, callback) => {
20
+ if (command === 'npm run export') {
21
+ // Simulate the PDF export process
22
+ fs.writeFileSync(pdfHelper.referencePdfPath, 'PDF content');
23
+ callback(null, 'PDF export successful');
24
+ } else {
25
+ callback(new Error('Unknown command'));
26
+ }
27
+ });
28
+ });
29
+
30
+ afterEach(function() {
31
+ // Restore all sinon stubs
32
+ sinon.restore();
33
+
34
+ // Restore the original PDF
35
+ pdfHelper.restoreReferencePdf();
36
+
37
+ // Clean up test PDF
38
+ pdfHelper.cleanupTestPdf();
39
+ });
40
+
41
+ it('should export a PDF with valid input', function(done) {
42
+ const resume = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'sample-resume.json'), 'utf-8'));
43
+ fs.writeFileSync(path.join(__dirname, '..', 'resume.json'), JSON.stringify(resume));
44
+
45
+ childProcess.exec('npm run export', (error, stdout, stderr) => {
46
+ if (error) {
47
+ return done(error);
48
+ }
49
+
50
+ assert(fs.existsSync(pdfHelper.referencePdfPath), 'PDF file should be generated');
51
+
52
+ // Read the file as a Buffer instead of utf-8 string since PDF is binary
53
+ const pdfExists = fs.existsSync(pdfHelper.referencePdfPath);
54
+ assert(pdfExists, 'PDF file should be generated');
55
+
56
+ done();
57
+ });
58
+ });
59
+
60
+ it('should handle errors during PDF export', function(done) {
61
+ // Restore the original stub and create a new one that simulates an error
62
+ sinon.restore();
63
+ execStub = sinon.stub(childProcess, 'exec');
64
+ execStub.callsFake((command, callback) => {
65
+ callback(new Error('Simulated error during PDF export'));
66
+ });
67
+
68
+ childProcess.exec('npm run export', (error, stdout, stderr) => {
69
+ assert(error, 'An error should be thrown during PDF export');
70
+ done();
71
+ });
72
+ });
73
+ });
@@ -0,0 +1,139 @@
1
+ const assert = require('assert');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { exec } = require('child_process');
5
+ const pdfParse = require('pdf-parse');
6
+ const pdfHelper = require('./utils/pdf-helper');
7
+
8
+ describe('Resume Structure and Metadata', function() {
9
+ this.timeout(10000);
10
+
11
+ before(function(done) {
12
+ // Backup the original PDF
13
+ pdfHelper.backupReferencePdf();
14
+
15
+ // Generate a test PDF
16
+ pdfHelper.generateTestPdf(done);
17
+ });
18
+
19
+ after(function() {
20
+ // Restore the original PDF
21
+ pdfHelper.restoreReferencePdf();
22
+
23
+ // Clean up test PDF
24
+ pdfHelper.cleanupTestPdf();
25
+ });
26
+
27
+ it('should maintain proper formatting', function(done) {
28
+ // Get the appropriate PDF path for testing
29
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
30
+
31
+ // Read and parse the PDF
32
+ const dataBuffer = fs.readFileSync(pdfToTest);
33
+ pdfParse(dataBuffer).then(data => {
34
+ const text = data.text;
35
+
36
+ // Check for formatting patterns - more flexible patterns
37
+ const datePatterns = [
38
+ /\d{4}\s*-\s*\d{4}/i, // 2014-2016
39
+ /\d{4}\s*-\s*Present/i, // 2014-Present
40
+ /\d{1,2}\/\d{4}\s*-\s*\d{1,2}\/\d{4}/i, // 05/2014-06/2016
41
+ /\d{1,2}\/\d{4}\s*-\s*Present/i, // 05/2014-Present
42
+ /[A-Z][a-z]{2}\s+\d{4}/ // May 2014
43
+ ];
44
+
45
+ const hasDatePattern = datePatterns.some(pattern => text.match(pattern));
46
+ assert(hasDatePattern, 'Some form of date formatting should be present');
47
+
48
+ // Check for email pattern - more flexible
49
+ const emailPattern = /\S+@\S+\.\S+/;
50
+ assert(text.match(emailPattern), 'Email should be properly formatted');
51
+
52
+ done();
53
+ }).catch(done);
54
+ });
55
+
56
+ it('should have consistent PDF metadata', function(done) {
57
+ // Get the appropriate PDF path for testing
58
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
59
+
60
+ // Read and parse the PDF
61
+ const dataBuffer = fs.readFileSync(pdfToTest);
62
+ pdfParse(dataBuffer).then(data => {
63
+ // Check PDF metadata
64
+ assert(data.info, 'PDF should have metadata');
65
+ assert(data.numpages >= 1, 'PDF should have at least 1 page');
66
+
67
+ // Check PDF size is reasonable (not too small, not too large)
68
+ const pdfSizeKB = dataBuffer.length / 1024;
69
+ assert(pdfSizeKB > 5, 'PDF should not be too small (< 5KB)');
70
+ assert(pdfSizeKB < 2000, 'PDF should not be too large (> 2000KB)');
71
+
72
+ done();
73
+ }).catch(done);
74
+ });
75
+
76
+ it('should have consistent file size with reference', function() {
77
+ // Get the appropriate PDF path for testing
78
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
79
+
80
+ // Get the size of the generated PDF
81
+ const pdfSize = fs.statSync(pdfToTest).size;
82
+
83
+ // Get the size of the reference PDF if it exists
84
+ let originalReferencePdfPath = path.join(__dirname, '..', 'reference.pdf');
85
+ if (!fs.existsSync(originalReferencePdfPath)) {
86
+ // If reference.pdf doesn't exist, create it from the current PDF
87
+ fs.copyFileSync(pdfToTest, originalReferencePdfPath);
88
+ this.skip(); // Skip this test for now
89
+ return;
90
+ }
91
+
92
+ const referencePdfSize = fs.statSync(originalReferencePdfPath).size;
93
+
94
+ // Allow for some variation in file size (±20%)
95
+ const sizeDifferencePercent = Math.abs(pdfSize - referencePdfSize) / referencePdfSize * 100;
96
+ assert(sizeDifferencePercent < 20, `PDF file size differs by ${sizeDifferencePercent.toFixed(2)}% from reference`);
97
+ });
98
+
99
+ it('should have sections in a logical order', function(done) {
100
+ // Get the appropriate PDF path for testing
101
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
102
+
103
+ // Read and parse the PDF
104
+ const dataBuffer = fs.readFileSync(pdfToTest);
105
+ pdfParse(dataBuffer).then(data => {
106
+ const text = data.text;
107
+ const resume = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'sample-resume.json'), 'utf-8'));
108
+
109
+ // Get the position of the name in the text
110
+ const namePos = text.indexOf(resume.basics.name);
111
+ assert(namePos !== -1, 'Name should be present');
112
+
113
+ // Define possible section headers
114
+ const sectionHeaders = [
115
+ 'Work Experience', 'Experience', 'Employment',
116
+ 'Education', 'Skills', 'Projects'
117
+ ];
118
+
119
+ // Find positions of each section header in the text
120
+ const positions = {};
121
+ sectionHeaders.forEach(header => {
122
+ const pos = text.indexOf(header);
123
+ if (pos !== -1) {
124
+ positions[header] = pos;
125
+ }
126
+ });
127
+
128
+ // Check that we found at least some sections
129
+ assert(Object.keys(positions).length >= 1, 'At least 1 section header should be present');
130
+
131
+ // Check that name appears before any section
132
+ for (const [header, pos] of Object.entries(positions)) {
133
+ assert(namePos < pos, `Name should appear before the "${header}" section`);
134
+ }
135
+
136
+ done();
137
+ }).catch(done);
138
+ });
139
+ });
@@ -0,0 +1,124 @@
1
+ const assert = require('assert');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const pdfParse = require('pdf-parse');
5
+ const pdfHelper = require('./utils/pdf-helper');
6
+
7
+ describe('Accessibility and Usability', function() {
8
+ this.timeout(10000);
9
+
10
+ before(function(done) {
11
+ // Backup the original PDF
12
+ pdfHelper.backupReferencePdf();
13
+
14
+ // Generate a test PDF
15
+ pdfHelper.generateTestPdf(done);
16
+ });
17
+
18
+ after(function() {
19
+ // Restore the original PDF
20
+ pdfHelper.restoreReferencePdf();
21
+
22
+ // Clean up test PDF
23
+ pdfHelper.cleanupTestPdf();
24
+ });
25
+
26
+ it('should have a reasonable file size', function() {
27
+ // Get the appropriate PDF path for testing
28
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
29
+
30
+ const stats = fs.statSync(pdfToTest);
31
+ const fileSizeInKB = stats.size / 1024;
32
+
33
+ console.log(`PDF file size: ${fileSizeInKB.toFixed(2)} KB`);
34
+
35
+ // Check that the file size is reasonable (not too small, not too large)
36
+ assert(fileSizeInKB > 10, 'PDF should not be too small (< 10KB)');
37
+ assert(fileSizeInKB < 1000, 'PDF should not be too large (> 1000KB)');
38
+ });
39
+
40
+ it('should have extractable text', function(done) {
41
+ // Get the appropriate PDF path for testing
42
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
43
+
44
+ // Read and parse the PDF
45
+ const dataBuffer = fs.readFileSync(pdfToTest);
46
+ pdfParse(dataBuffer).then(data => {
47
+ const text = data.text;
48
+
49
+ // Check that the PDF has extractable text (important for accessibility)
50
+ assert(text.length > 100, 'PDF should have extractable text');
51
+
52
+ // Check that the text contains meaningful content
53
+ assert(text.split(/\s+/).length > 50, 'PDF should contain a reasonable amount of text');
54
+
55
+ done();
56
+ }).catch(done);
57
+ });
58
+
59
+ it('should have appropriate metadata', function(done) {
60
+ // Get the appropriate PDF path for testing
61
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
62
+
63
+ // Read and parse the PDF
64
+ const dataBuffer = fs.readFileSync(pdfToTest);
65
+ pdfParse(dataBuffer).then(data => {
66
+ const info = data.info;
67
+
68
+ // Check that the PDF has basic metadata
69
+ assert(info, 'PDF should have metadata');
70
+
71
+ // Log the metadata for informational purposes
72
+ console.log('PDF metadata:', JSON.stringify(info, null, 2));
73
+
74
+ done();
75
+ }).catch(done);
76
+ });
77
+
78
+ it('should have a reasonable page count', function(done) {
79
+ // Get the appropriate PDF path for testing
80
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
81
+
82
+ // Read and parse the PDF
83
+ const dataBuffer = fs.readFileSync(pdfToTest);
84
+ pdfParse(dataBuffer).then(data => {
85
+ const pageCount = data.numpages;
86
+
87
+ console.log(`PDF page count: ${pageCount}`);
88
+
89
+ // Check that the PDF has a reasonable number of pages
90
+ assert(pageCount >= 1, 'PDF should have at least 1 page');
91
+ assert(pageCount <= 3, 'PDF should not have more than 3 pages for a typical resume');
92
+
93
+ done();
94
+ }).catch(done);
95
+ });
96
+
97
+ it('should include contact information', function(done) {
98
+ // Get the appropriate PDF path for testing
99
+ const pdfToTest = pdfHelper.getPdfPathForTesting();
100
+
101
+ // Read and parse the PDF
102
+ const dataBuffer = fs.readFileSync(pdfToTest);
103
+ pdfParse(dataBuffer).then(data => {
104
+ const text = data.text;
105
+ const resume = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'sample-resume.json'), 'utf-8'));
106
+
107
+ // Check that contact information is included
108
+ assert(text.includes(resume.basics.email), 'Email should be included');
109
+
110
+ // Phone might be formatted differently
111
+ const phoneDigits = resume.basics.phone.replace(/\D/g, '');
112
+ const hasPhone = text.includes(phoneDigits) ||
113
+ text.includes(phoneDigits.substring(phoneDigits.length - 4));
114
+ assert(hasPhone, 'Phone number should be included');
115
+
116
+ // Website might be formatted differently
117
+ const websiteDomain = resume.basics.website.replace(/https?:\/\//i, '').replace(/\/$/, '');
118
+ const hasWebsite = text.includes(websiteDomain) || text.includes(resume.basics.website);
119
+ assert(hasWebsite, 'Website should be included');
120
+
121
+ done();
122
+ }).catch(done);
123
+ });
124
+ });
@@ -0,0 +1,73 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { exec } = require('child_process');
4
+
5
+ // Paths
6
+ const referencePdfPath = path.join(__dirname, '..', '..', 'resume.pdf');
7
+ const testPdfPath = path.join(__dirname, '..', '..', 'test-resume.pdf');
8
+
9
+ // Save original PDF if it exists
10
+ let originalPdfExists = false;
11
+ let originalPdfContent = null;
12
+
13
+ function backupReferencePdf() {
14
+ originalPdfExists = fs.existsSync(referencePdfPath);
15
+ if (originalPdfExists) {
16
+ originalPdfContent = fs.readFileSync(referencePdfPath);
17
+ }
18
+ }
19
+
20
+ function restoreReferencePdf() {
21
+ if (originalPdfExists && originalPdfContent) {
22
+ fs.writeFileSync(referencePdfPath, originalPdfContent);
23
+ } else if (fs.existsSync(referencePdfPath) && !originalPdfExists) {
24
+ fs.unlinkSync(referencePdfPath);
25
+ }
26
+ }
27
+
28
+ function cleanupTestPdf() {
29
+ if (fs.existsSync(testPdfPath)) {
30
+ fs.unlinkSync(testPdfPath);
31
+ }
32
+ }
33
+
34
+ function generateTestPdf(callback) {
35
+ const resume = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'sample-resume.json'), 'utf-8'));
36
+ fs.writeFileSync(path.join(__dirname, '..', '..', 'resume.json'), JSON.stringify(resume));
37
+
38
+ // Try to generate a test PDF with a different name using the correct command format
39
+ exec(`resume export --theme . ${testPdfPath}`, (error) => {
40
+ if (error) {
41
+ console.warn('Warning: Could not generate test PDF with custom name. Trying standard export.');
42
+
43
+ // Fall back to standard export command from package.json
44
+ exec('npm run export', (stdError) => {
45
+ if (stdError) {
46
+ return callback(new Error('Failed to generate PDF for testing: ' + stdError.message));
47
+ }
48
+
49
+ if (!fs.existsSync(referencePdfPath)) {
50
+ return callback(new Error('PDF was not generated at expected path: ' + referencePdfPath));
51
+ }
52
+
53
+ callback();
54
+ });
55
+ } else {
56
+ callback();
57
+ }
58
+ });
59
+ }
60
+
61
+ function getPdfPathForTesting() {
62
+ return fs.existsSync(testPdfPath) ? testPdfPath : referencePdfPath;
63
+ }
64
+
65
+ module.exports = {
66
+ referencePdfPath,
67
+ testPdfPath,
68
+ backupReferencePdf,
69
+ restoreReferencePdf,
70
+ cleanupTestPdf,
71
+ generateTestPdf,
72
+ getPdfPathForTesting
73
+ };
package/views/awards.hbs CHANGED
@@ -6,7 +6,7 @@
6
6
  <li>
7
7
  {{#if title}}
8
8
  {{#if summary}}
9
- <strong>{{title}}</strong>: {{summary}}
9
+ <strong>{{title}}</strong>: {{{summary}}}
10
10
  {{else}}
11
11
  <strong>{{title}}</strong>
12
12
  {{/if}}
package/views/basics.hbs CHANGED
@@ -18,7 +18,7 @@
18
18
  {{#if summary}}
19
19
  <div class="summary">
20
20
  <h2>Summary</h2>
21
- <p>{{summary}}</p>
21
+ <p>{{{summary}}}</p>
22
22
  </div>
23
23
  {{/if}}
24
24
  </section>
@@ -22,7 +22,7 @@
22
22
  {{#if courses.length}}
23
23
  <ul class="courses">
24
24
  {{#each courses}}
25
- <li>{{.}}</li>
25
+ <li>{{{.}}}</li>
26
26
  {{/each}}
27
27
  </ul>
28
28
  {{/if}}
@@ -5,14 +5,14 @@
5
5
  {{#> item title=organization subtitle=position location=location startDate=startDate endDate=endDate }}
6
6
  {{#if summary}}
7
7
  <div class="summary">
8
- <p>{{summary}}</p>
8
+ <p>{{{summary}}}</p>
9
9
  </div>
10
10
  {{/if}}
11
11
 
12
12
  {{#if highlights.length}}
13
13
  <ul class="highlights">
14
14
  {{#each highlights}}
15
- <li>{{.}}</li>
15
+ <li>{{{.}}}</li>
16
16
  {{/each}}
17
17
  </ul>
18
18
  {{/if}}
package/views/work.hbs CHANGED
@@ -7,7 +7,7 @@
7
7
  {{#if highlights.length}}
8
8
  <ul class="highlights">
9
9
  {{#each highlights}}
10
- <li>{{.}}</li>
10
+ <li>{{{.}}}</li>
11
11
  {{/each}}
12
12
  </ul>
13
13
  {{/if}}
@@ -1,11 +0,0 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
3
- # Please see the documentation for all configuration options:
4
- # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
-
6
- version: 2
7
- updates:
8
- - package-ecosystem: "npm"
9
- directory: "/" # Location of package manifests
10
- schedule:
11
- interval: "weekly"
Binary file
Binary file
Binary file
package/resume.old.png DELETED
Binary file
package/resume.png DELETED
Binary file
@@ -1,156 +0,0 @@
1
- {
2
- "basics": {
3
- "name": "Richard Hendriks",
4
- "label": "Programmer",
5
- "picture": "http://www.piedpiper.com/app/themes/pied-piper/dist/images/richard.png",
6
- "email": "richard.hendriks@piedpiper.com",
7
- "phone": "(912) 555-4321",
8
- "website": "http://piedpiper.com",
9
- "summary": "Richard hails from Tulsa. He has earned degrees from the University of Oklahoma and Stanford. (Go Sooners and Cardinals!) Before starting Pied Piper, he worked for Hooli as a part time software developer. While his work focuses on applied information theory, mostly optimizing lossless compression schema of both the length-limited and adaptive variants, his non-work interests range widely, everything from quantum computing to chaos theory. He could tell you about it, but THAT would NOT be a “length-limited” conversation!",
10
- "location": {
11
- "address": "Newell Road",
12
- "postalCode": "94303",
13
- "city": "Palo Alto",
14
- "countryCode": "US",
15
- "region": "CA"
16
- },
17
- "profiles": [
18
- {
19
- "network": "Twitter",
20
- "username": "siliconHBO",
21
- "url": "https://twitter.com/siliconHBO"
22
- },
23
- {
24
- "network": "Facebook",
25
- "username": "SiliconHBO",
26
- "url": "https://www.facebook.com/SiliconHBO"
27
- },
28
- {
29
- "network": "Instagram",
30
- "username": "siliconhbo",
31
- "url": "https://www.instagram.com/siliconhbo/"
32
- }
33
- ]
34
- },
35
- "work": [
36
- {
37
- "company": "Pied Piper",
38
- "position": "CEO/President",
39
- "website": "http://piedpiper.com",
40
- "startDate": "2014-04-13",
41
- "summary": "Pied Piper is a multi-platform technology based on a proprietary universal compression algorithm that has consistently fielded high Weisman Scores™ that are not merely competitive, but approach the theoretical limit of lossless compression.",
42
- "highlights": [
43
- "Build an algorithm for artist to detect if their music was violating copy right infringement laws",
44
- "Successfully won Techcrunch Disrupt",
45
- "Optimized an algorithm that holds the current world record for Weisman Scores"
46
- ]
47
- },
48
- {
49
- "company": "Hooli",
50
- "position": "Senior Software Engineer",
51
- "website": "http://www.hooli.xyz/",
52
- "startDate": "2014-01-01",
53
- "endDate": "2014-04-06",
54
- "highlights": [
55
- "Worked on optimizing the backend algorithms for Hooli"
56
- ]
57
- },
58
- {
59
- "company": "Hooli",
60
- "position": "Software Engineer",
61
- "website": "http://www.hooli.xyz/",
62
- "startDate": "2013-01-01",
63
- "endDate": "2014-01-01",
64
- "highlights": [
65
- "Contributed bugfixes and smaller features for Hooli"
66
- ]
67
- }
68
- ],
69
- "volunteer": [
70
- {
71
- "organization": "CoderDojo",
72
- "position": "Teacher",
73
- "website": "http://coderdojo.com/",
74
- "startDate": "2012-01-01",
75
- "endDate": "2013-01-01",
76
- "summary": "Global movement of free coding clubs for young people.",
77
- "highlights": [
78
- "Awarded 'Teacher of the Month'"
79
- ]
80
- }
81
- ],
82
- "education": [
83
- {
84
- "institution": "Stanford",
85
- "area": "Computer Science",
86
- "studyType": "B.S",
87
- "location": "Palo Alto, CA",
88
- "specialization": "Machine Learning",
89
- "startDate": "2011-06-01",
90
- "endDate": "2014-01-01",
91
- "gpa": "GPA 4.0",
92
- "courses": [
93
- "DB1101 - Basic SQL",
94
- "CS2011 - Java Introduction"
95
- ]
96
- }
97
- ],
98
- "awards": [
99
- {
100
- "title": "Digital Compression Pioneer Award",
101
- "date": "2014-11-01",
102
- "awarder": "Techcrunch",
103
- "summary": "There is no spoon."
104
- }
105
- ],
106
- "publications": [
107
- {
108
- "name": "Video compression for 3d media",
109
- "publisher": "Hooli",
110
- "releaseDate": "2014-10-01",
111
- "website": "http://en.wikipedia.org/wiki/Silicon_Valley_(TV_series)",
112
- "summary": "Innovative middle-out compression algorithm that changes the way we store data."
113
- }
114
- ],
115
- "skills": [
116
- {
117
- "name": "Web Development",
118
- "level": "Master",
119
- "keywords": [
120
- "HTML",
121
- "CSS",
122
- "Javascript"
123
- ]
124
- },
125
- {
126
- "name": "Compression",
127
- "level": "Master",
128
- "keywords": [
129
- "Mpeg",
130
- "MP4",
131
- "GIF"
132
- ]
133
- }
134
- ],
135
- "languages": [
136
- {
137
- "language": "English",
138
- "fluency": "Native speaker"
139
- }
140
- ],
141
- "interests": [
142
- {
143
- "name": "Wildlife",
144
- "keywords": [
145
- "Ferrets",
146
- "Unicorns"
147
- ]
148
- }
149
- ],
150
- "references": [
151
- {
152
- "name": "Erlich Bachman",
153
- "reference": "It is my pleasure to recommend Richard. That is all."
154
- }
155
- ]
156
- }