Skip to content

Commit 6b56f5e

Browse files
authored
Merge pull request #332 from smalruby/fix-load-project-with-id
feat: support hash paramter to load scratch project
2 parents 50dd881 + c19d235 commit 6b56f5e

File tree

9 files changed

+71
-6
lines changed

9 files changed

+71
-6
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ npm-*
2323

2424
# Downloaded during "npm install"
2525
/static/microbit
26+
27+
/tmp

src/lib/project-fetcher-hoc.jsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import {intlShape, injectIntl} from 'react-intl';
44
import bindAll from 'lodash.bindall';
55
import {connect} from 'react-redux';
6+
import xhr from 'xhr';
67

78
import {setProjectUnchanged} from '../reducers/project-changed';
89
import {
@@ -72,6 +73,41 @@ const ProjectFetcherHOC = function (WrappedComponent) {
7273
}
7374
}
7475
fetchProject (projectId, loadingState) {
76+
if (!this.props.projectToken) {
77+
const errorHandler = err => {
78+
this.props.onError(err);
79+
log.error(err);
80+
};
81+
return new Promise((resolve, reject) => {
82+
const options = {
83+
method: 'GET',
84+
uri: `https://api.smalruby.app/scratch-api-proxy/projects/${projectId}`,
85+
json: true
86+
};
87+
xhr(options, (error, response) => {
88+
if (error || response.statusCode !== 200) {
89+
return reject(new Error(response.status));
90+
}
91+
resolve(response.body.project_token);
92+
});
93+
})
94+
.then(projectToken => {
95+
storage.setProjectToken(projectToken);
96+
storage
97+
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
98+
.then(projectAsset => {
99+
if (projectAsset) {
100+
this.props.onFetchedProjectData(projectAsset.data, loadingState);
101+
} else {
102+
// Treat failure to load as an error
103+
// Throw to be caught by catch later on
104+
throw new Error('Could not find project');
105+
}
106+
})
107+
.catch(errorHandler);
108+
}, errorHandler)
109+
.catch(errorHandler);
110+
}
75111
return storage
76112
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
77113
.then(projectAsset => {

test/integration/blocks.test.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
getLogs,
1414
Key,
1515
loadUri,
16+
notExistsByXpath,
1617
rightClickText,
1718
scope
1819
} = new SeleniumHelper();
@@ -35,6 +36,7 @@ describe('Working with the blocks', () => {
3536

3637
test('Blocks report when clicked in the toolbox', async () => {
3738
await loadUri(uri);
39+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
3840
await clickText('Code');
3941
await clickBlocksCategory('Operators');
4042
await clickText('join', scope.blocksTab); // Click "join <hello> <world>" block
@@ -45,6 +47,7 @@ describe('Working with the blocks', () => {
4547

4648
test('Switching sprites updates the block menus', async () => {
4749
await loadUri(uri);
50+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
4851
await clickBlocksCategory('Sound');
4952
// "Meow" sound block should be visible
5053
await findByText('Meow', scope.blocksTab);
@@ -60,6 +63,7 @@ describe('Working with the blocks', () => {
6063

6164
test('Creating variables', async () => {
6265
await loadUri(uri);
66+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
6367
await clickText('Code');
6468
await clickBlocksCategory('Variables');
6569

@@ -107,6 +111,7 @@ describe('Working with the blocks', () => {
107111

108112
test('Creating a list', async () => {
109113
await loadUri(uri);
114+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
110115
await clickText('Code');
111116
await clickBlocksCategory('Variables');
112117

@@ -147,6 +152,7 @@ describe('Working with the blocks', () => {
147152

148153
test('Custom procedures', async () => {
149154
await loadUri(uri);
155+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
150156
await clickBlocksCategory('My Blocks');
151157
await clickText('Make a Block');
152158
// Click on the "add an input" buttons
@@ -164,6 +170,7 @@ describe('Working with the blocks', () => {
164170

165171
test('Adding an extension', async () => {
166172
await loadUri(uri);
173+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
167174
await clickXpath('//button[@title="Add Extension"]');
168175

169176
await clickText('Pen');
@@ -177,6 +184,7 @@ describe('Working with the blocks', () => {
177184

178185
test('Record option from sound block menu opens sound recorder', async () => {
179186
await loadUri(uri);
187+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
180188
await clickText('Code');
181189
await clickBlocksCategory('Sound');
182190
await clickText('Meow', scope.blocksTab); // Click "play sound <Meow> until done" block
@@ -192,6 +200,7 @@ describe('Working with the blocks', () => {
192200

193201
test('Renaming costume changes the default costume name in the toolbox', async () => {
194202
await loadUri(uri);
203+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
195204

196205
// Rename the costume
197206
await clickText('Costumes');
@@ -209,8 +218,9 @@ describe('Working with the blocks', () => {
209218
await clickText('newname', scope.blocksTab);
210219
});
211220

212-
test.skip('Renaming costume with a special character should not break toolbox', async () => {
221+
test('Renaming costume with a special character should not break toolbox', async () => {
213222
await loadUri(uri);
223+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
214224

215225
// Rename the costume
216226
await clickText('Costumes');
@@ -232,6 +242,7 @@ describe('Working with the blocks', () => {
232242

233243
test('Adding costumes DOES update the default costume name in the toolbox', async () => {
234244
await loadUri(uri);
245+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
235246

236247
// By default, costume2 is in the costume tab
237248
await clickBlocksCategory('Looks');
@@ -256,6 +267,7 @@ describe('Working with the blocks', () => {
256267
// Skipped because it was flakey on travis, but seems to run locally ok
257268
test('Adding a sound DOES update the default sound name in the toolbox', async () => {
258269
await loadUri(uri);
270+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
259271
await clickText('Sounds');
260272
await clickXpath('//button[@aria-label="Choose a Sound"]');
261273
await clickText('A Bass', scope.modal); // Should close the modal
@@ -271,6 +283,7 @@ describe('Working with the blocks', () => {
271283
test('"See inside" after being on project page re-initializing variables', async () => {
272284
const playerUri = path.resolve(__dirname, '../../build/player.html');
273285
await loadUri(playerUri);
286+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
274287
await clickText('See inside');
275288
await clickBlocksCategory('Variables');
276289
await clickText('my\u00A0variable');
@@ -285,6 +298,7 @@ describe('Working with the blocks', () => {
285298
// Regression test for switching editor tabs causing toolbox to stop updating
286299
test('Creating variables after adding extensions updates the toolbox', async () => {
287300
await loadUri(uri);
301+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
288302
await clickText('Costumes');
289303
await clickText('Code');
290304
await clickBlocksCategory('Variables');
@@ -300,6 +314,7 @@ describe('Working with the blocks', () => {
300314
const changeVariableByScope = "*[@data-id='data_changevariableby']";
301315

302316
await loadUri(uri);
317+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
303318

304319
await clickText('Code');
305320
await clickBlocksCategory('Variables');

test/integration/project-loading.test.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {
99
getDriver,
1010
getLogs,
1111
loadUri,
12+
notExistsByXpath,
1213
scope
1314
} = new SeleniumHelper();
1415

@@ -79,28 +80,31 @@ describe('Loading scratch gui', () => {
7980
await expect(logs).toEqual([]);
8081
});
8182

82-
// skipping because this test fails frequently on CI; might need "wait(until.elementLocated" or similar
83-
// error message is "stale element reference: element is not attached to the page document"
84-
test.skip('Creating new project resets active tab to Code tab', async () => {
83+
test('Creating new project resets active tab to Code tab', async () => {
8584
await loadUri(uri);
85+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
8686
await findByXpath('//*[span[text()="Costumes"]]');
8787
await clickText('Costumes');
8888
await clickXpath(FILE_MENU_XPATH);
8989
await clickXpath('//li[span[text()="New"]]');
90+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
9091
await findByXpath('//div[@class="scratchCategoryMenu"]');
9192
await clickText('Operators', scope.blocksTab);
9293
});
9394

9495
test('Not logged in->made no changes to project->create new project should not show alert', async () => {
9596
await loadUri(uri);
97+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
9698
await clickXpath(FILE_MENU_XPATH);
9799
await clickXpath('//li[span[text()="New"]]');
100+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
98101
await findByXpath('//*[div[@class="scratchCategoryMenu"]]');
99102
await clickText('Operators', scope.blocksTab);
100103
});
101104

102-
test.skip('Not logged in->made a change to project->create new project should show alert', async () => {
105+
test('Not logged in->made a change to project->create new project should show alert', async () => {
103106
await loadUri(uri);
107+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
104108
await clickText('Sounds');
105109
await clickXpath('//button[@aria-label="Choose a Sound"]');
106110
await clickText('A Bass', scope.modal); // Should close the modal
@@ -110,6 +114,7 @@ describe('Loading scratch gui', () => {
110114
driver.switchTo()
111115
.alert()
112116
.accept();
117+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
113118
await findByXpath('//*[div[@class="scratchCategoryMenu"]]');
114119
await clickText('Operators', scope.blocksTab);
115120
});

test/integration/removed-trademarks.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,5 @@ describe('Removed trademarks (ex: Scratch Cat)', () => {
5757
expect(await notExistsByXpath(`//*[span[contains(text(), "${costumePrefix}")]]`)).toBeTruthy();
5858
searchElement.clear();
5959
}
60-
}, 60 * 1000);
60+
}, 120 * 1000);
6161
});

test/integration/ruby-tab/control.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
scope,
1212
getDriver,
1313
loadUri,
14+
notExistsByXpath,
1415
urlFor
1516
} = seleniumHelper;
1617

@@ -34,6 +35,7 @@ describe('Ruby Tab: Control category blocks', () => {
3435

3536
test('Ruby -> Code -> Ruby', async () => {
3637
await loadUri(urlFor('/'));
38+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
3739
await clickXpath('//button[@aria-label="Choose a Sprite"]');
3840
await clickText('Abby', scope.modal);
3941
await findByXpath(

test/integration/ruby-tab/motion.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {
99
scope,
1010
getDriver,
1111
loadUri,
12+
notExistsByXpath,
1213
urlFor
1314
} = seleniumHelper;
1415

@@ -30,6 +31,7 @@ describe('Ruby Tab: Motion category blocks', () => {
3031

3132
test('Ruby -> Code -> Ruby', async () => {
3233
await loadUri(urlFor('/'));
34+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
3335
await clickXpath('//button[@aria-label="Choose a Sprite"]');
3436
await clickText('Abby', scope.modal);
3537
await clickText('Sprite1', scope.spriteItems);

test/integration/ruby-tab/sensing.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {
99
scope,
1010
getDriver,
1111
loadUri,
12+
notExistsByXpath,
1213
urlFor
1314
} = seleniumHelper;
1415

@@ -30,6 +31,7 @@ describe('Ruby Tab: Control category blocks', () => {
3031

3132
test('Ruby -> Code -> Ruby', async () => {
3233
await loadUri(urlFor('/'));
34+
await notExistsByXpath('//*[div[contains(@class, "loader_background")]]');
3335
await clickXpath('//button[@aria-label="Choose a Sprite"]');
3436
await clickText('Abby', scope.modal);
3537
await clickText('Sprite1', scope.spriteItems);

test/unit/util/project-fetcher-hoc.test.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ describe('ProjectFetcherHOC', () => {
4848
);
4949
mounted.setProps({
5050
reduxProjectId: '100',
51+
projectToken: '12345',
5152
isFetchingWithId: true,
5253
loadingState: LoadingState.FETCHING_WITH_ID
5354
});

0 commit comments

Comments
 (0)