How to await a build task in a VS Code extension? - visual-studio-code

let result = await vscode.commands.executeCommand('workbench.action.tasks.build');
resolves immediately.
How can I await a build task with VS Code API?

I figured it out! Tasks cannot be awaited from vscode.tasks.executeTask, but we can await vscode.tasks.onDidEndTask and check if ended task is our task.
async function executeBuildTask(task: vscode.Task) {
const execution = await vscode.tasks.executeTask(task);
return new Promise<void>(resolve => {
let disposable = vscode.tasks.onDidEndTask(e => {
if (e.execution.task.group === vscode.TaskGroup.Build) {
disposable.dispose();
resolve();
}
});
});
}
async function getBuildTasks() {
return new Promise<vscode.Task[]>(resolve => {
vscode.tasks.fetchTasks().then((tasks) => {
resolve(tasks.filter((task) => task.group === vscode.TaskGroup.Build));
});
});
}
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('extension.helloWorld', async () => {
const buildTasks = await getBuildTasks();
await executeBuildTask(buildTasks[0]);
}));
}
Note that currently there is a bug #96643, which prevents us from doing a comparison of vscode.Task objects: if (e.execution.task === execution.task) { ... }

I think this depends on how the main command is executed in the extension.ts
Being new to JS/TS, I may be wrong here, but just trying to help:
make sure the vscode.command.registerCommand is not asyncronous, like below:
context.subscriptions.push(vscode.commands.registerCommand('extension.openSettings', () => {
return vscode.commands.executeCommand("workbench.action.openSettings", "settingsName");
}));
This would be compared to something async, like below:
context.subscriptions.push(vscode.commands.registerCommand('extension.removeHost', async (hostID) => {
const bigipHosts: Array<string> | undefined = vscode.workspace.getConfiguration().get('extension.hosts');
const newHosts = Hosts?.filter( item => item != hostID.label)
await vscode.workspace.getConfiguration().update('f5-fast.hosts', newBigipHosts, vscode.ConfigurationTarget.Global);
hostsTreeProvider.refresh();
}));

Related

Riverpod : future provider is stuck on loading

Describe the bug
when executing the provider with ref.read or ref.watch the result is the same , it is stuck on the loading block , while testing the api in postman works fine , the funny thing is that the api call gets executed and whenever i print something inside it it appears in the console
To Reproduce
in presentation layer
onpressed:()=>ref
.read(getPatientProvider(
r.api_token))
.when(data: (data) {
data.fold(
(l) => print(
"something wrong happened"),
(r) async {
print(r.id);
print("hello");
patient.value = patient.value
.copyWith(
name: r.name,
aliid: r.id,
appointments: r
.patient_appointments,
fcmtoken: token);
ref.read(docexist(r.id)).when(
loading: () =>
print("loading"),
error: (error, _) =>
print(error),
data: (data) async {
print("heloo");
if (data.isEmpty) {
print(
"data is not empty");
} else {
return print(
"logged in normally");
}
});
});
}, error: (error, _) {
print(error);
}, loading: () {
print("object");
})
Provider with riverpod generator
#riverpod
Future<Either<ApiFailures, dynamic>> getPatient(
GetPatientRef ref, String token) async {
final patientProvider = ref.watch(patientRepositoryProvider);
return patientProvider.getInfo(token);
}
infrastructure layer
#override
Future<Either<ApiFailures, dynamic>> getInfo(String token) {
var dio = Dio();
final result = TaskEither<ApiFailures, PatientModel>(() async {
try {
final response = await dio.get(
"https://xxxxxxxx/GetInfo?api_token=$token");
if (response.data == null) {
return const Left(ApiFailures.notfound());
} else {
PatientModel patientModel =
PatientModel.fromJson(response.data["User"]);
return Right(patientModel);
}
} catch (err, st) {
final message = 'error ${err.runtimeType}]';
if (kDebugMode) log(message, error: err, stackTrace: st);
if (err is DioError) {
return Left(ApiFailures.fromDioError(error: err));
}
return const Left(ApiFailures.internalError());
}
});
return result.map((r) => r).run();
}
Expected behavior
it should get the data as always
Calling when inside a click handler such as onPressed as you did does not make sense.
"when" does not wait for the future to complete. It executes immediately based on the current status of the future.
Considering that when you call it, you just triggered the future, then the future at that time will always be in a loading state.
What you want is something like async/await, where you can wait until the completion of your future.
You could do that with:
onPressed: () async {
final value = await ref.read(provider.future);
}

Testing VSCode Extension with mocha and puppeteer behaves strangely

I'm writing test code for VS Code Extension with mocha and puppeteer.
This code behaves oddly.
This test will change the result without editing the code.
Sometimes test is failed because timeout of const e = await global.page.waitForSelector("iframe", { timeout: 10000 });, and sometimes it succeeds.
Changing the value of the slowMo option of puppeteer.launch() after several timeouts may result in a successful test. (not every time)  
How can I make this stable?
describe("test", () => {
before(async () => {
global.browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:9229',
defaultViewport: null,
slowMo: 10
});
global.page = (await global.browser.pages())[0];
});
after(async () => {
if (global.page) {
await global.page.close();
}
if (global.browser) {
global.browser.disconnect();
}
});
describe("open file", () => {
before(async () => {
// open file with my custom editor
const uri = Uri.file(path.join(__dirname, "test.pdf"));
await commands.executeCommand("vscode.open", uri);
// get editor's dom content
const e = await global.page.waitForSelector("iframe", { timeout: 10000 });
const ec = await editor.contentFrame();
const ee = await editorFrame.waitForSelector("iframe#active-frame", { timeout: 10000 });
const eec = await editorContent.contentFrame();
global.dom = eec;
});
after(async () => {
await commands.executeCommand("workbench.action.closeActiveEditor");
});
it("should have x", async () => {
const x = await global.doc.waitForSelector("#x", { timeout: 5000 });
assert.ok(x);
});
});
});

Firebase cloud functions not waiting for forEach to complete before jumping to the next then

Been trying to copy subcollections of a collection into another collection. The code below is aimed at that, but jumps from the first then and logs out "Done" without logging out anything before.
So the question is what is not correct here?
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
try {
await db.collection("users").get().then((query) => {
return query.forEach(async (doc) => {
console.log("Here"); //This doesn't print
const polCollection = await db.collection("users").doc(doc.id).collection("xyz").get();
if (polCollection.docs.length > 0) { //This checks if any subcollections
for (const x of polCollection.docs) { //This copies them into a doc in the copy collection
db.collection("CopyUsers")
.doc(doc.id)
.set({ x : x.data() }, { merge: true });
}
}
});
})
.then(() => {
console.log("Done"); //This is the only thing that prints in the console
res.end();
})
.catch((e) => {
console.log("e", e);
res.end();
});
} catch (error) {
console.log("error", error);
res.end();
}
});
After the suggestion below, it now looks as follows:
exports = module.exports = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
const promises = [];
let count = 0;
let size = 0;
return await admin
.firestore()
.collection("testUsers")
.get()
.then((query) => {
console.log("query length:", query.size); //prints x of users
size = query.size;
query.forEach(async (doc) => {
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
await admin
.firestore()
.collection("testUsers")
.doc(doc.id)
.collection("xyz")
.get()
.then(async (polCollection) => {
if (polCollection.docs.length > 0) {
for (const x of polCollection.docs) {
return await admin
.firestore()
.collection("testBackUpUsers")
.doc(doc.id)
.set(
{ xyz: x.data() },
{ merge: true }
);
}
} else {
return;
}
})
.catch((e) => console.log("error from then after get xyz", e));
};
count++;
return promises.push(promise);
});
return promises;
})
.then(async (promises) => {
if (size <= count) {
console.log("running return Promise.all(promises)", promises.length); //prints number of promises = users
return Promise.all(promises);
}
})
.catch((e) => console.log("err from the last catch", e));
});
Any thoughts?
Unfortunately the forEach iterator does not support async/await. Even if you write an await inside it will just go trough it without waiting on the execution.
I would recommend to use Promise.all. That would also execute the code in parallel and would finish faster.
If you would only change data you could also use a batch change but in your example you first get the data and then change it.
Here is an example how you could write your code:
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
const promises = [];
try {
const query = await db.collection("users").get();
query.forEach((doc) => {
console.log("doc", doc);
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
const polCollection = await db
.collection("users")
.doc(doc.id)
.collection("xyz")
.get();
if (polCollection.docs.length > 0) {
//This checks if any subcollections
for (const x of polCollection.docs) {
//This copies them into a doc in the copy collection
await db
.collection("CopyUsers")
.doc(doc.id)
.set({ x: x.data() }, { merge: true });
}
}
};
promises.push(promise);
});
console.log("promises", promises);
await Promise.all(promises);
console.log("Done");
res.end();
} catch (error) {
console.log("error", error);
res.end();
}
});

Why resolving an async promise with a .map() function doesn't work for GET with parameters?

I am not sure how to express my question correctly.
Basically resolving an async promise with a .map() function works for simple get functions while it doesn't work for get functions with parameter.
Basically, in this case, router.get('/' ... the following works:
import axios from 'axios'
const url = 'http://localhost:3000/api/library/'
class libraryService {
// Get stories
static getStories () {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url)
const data = res.data
resolve(
data.map(story => ({
...story
}))
)
} catch (err) {
reject(err)
}
})
}
export default libraryService
While in this case, router.get('/:story_name' ..., this variation doesn't work:
class readService {
// Get story to read
static getStoryToRead (storyName) {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url + storyName)
const data = res.data
resolve(
data.map(selectedStory => ({
...selectedStory
}))
...
In here I get an error: 'data.map is not a function'.
Changing to data.products.map() will return an error 'Cannot read property 'map' of undefined'.
However resolving data without .map() function will work on all cases:
try {
const res = await axios.get(...)
const data = res.data
resolve(
data
)
...
Why this is happening and is it correct to just use resolve(data)?
You seem to be asking for a single story in the case that doesn't work. So instead of an array of stories, presuambly you're getting just the one story that you asked for. There's no reason to try to use map.
Minimal changes (but keep reading):
// Minimal changes, but keep reading...
static getStoryToRead (storyName) {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url + storyName);
resolve(res.data);
} catch (err) {
reject(err);
}
});
}
But, both of those functions demonstrate the Promise creation antipattern. You already have a promise, work with it. In this case, you'd probably do that by making the functions async:
static async getStories () {
const {data} = await axios.get(url);
return data.map(story => ({ // Why copy the story objects?
...story
}));
}
static async getStoryToRead (storyName) {
const {data} = await axios.get(url + storyName));
return data;
}
Or with non-async functions:
static getStories () {
return axios.get(url)
.then(({data}) => data.map(story => ({...story}))); // Why copy the story objects?
}
static getStoryToRead (storyName) {
return axios.get(url + storyName))
.then(({data}) => data);
}

Mocking classes with Jest

Given I have a class that calls a function doStuff like so:
const myService = require(`./myService`),
service = new myService();
exports.doStuff = async (callback) => {
try {
const data = await service.doOtherStuff(); //I want to mock this
return callback(null, data);
} catch (err) {
callback(new Error(` > feed failed for ${key}, error: ${err}`));
}
};
my tests work like this:
const myClass = require(`../index`);
jest.mock(`./../myService`, () => {
return function() {
return {
doOtherStuff: () => {
return 1;
}
};
};
});
describe(`service-tests - index`, () => {
test(`doStuff and test otherStuff`, async () => {
const result = await myClass.doStuff((err, data) => {
return data;
});
expect(result).toBe(1);
});
});
And my service:
class myService{
constructor() {
//Do constructor stuff
}
async doOtherStuff() {
//Do other stuff
}
This works but now I just have my mock on this file and not by test.
What I need is to have my mock be changable test by test but cannot seem to figure out how this works together with require.
I tried just doing jest.mock('./../myService') and have the mockImplementation on the beforeAll but that will keep my function in mocked as the automatic mock it seems, returning undefined
Anyone ever done this before?
If you want to mock a method within a class, which it looks like you're doing, I would suggest you use jest.spyOn. It's simple, and you can mock the return value to be whatever you want per test.
const myClass = require('../index');
const myService = require('../myService');
describe('service-tests - index', () => {
let doOtherStuffMock;
beforeAll(() => {
doOtherStuffMock = jest.spyOn(myService.prototype, 'doOtherStuff');
});
it('mocks doOtherStuff one way', async () => {
doOtherStuffMock.mockResolvedValue('I am a mocked value');
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe("I am a mocked value");
});
it('mocks doOtherStuff another way', async () => {
doOtherStuffMock.mockResolvedValue('I am DIFFERENT mocked value');
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe('I am DIFFERENT mocked value');
});
});
Update
I'll leave this answer here since both of these approaches work and they can be useful...
...but for this particular case #Daniel is right, mocking the prototype method is easiest
An easy way to handle this is to mock myService.js as a singleton...
...then you can grab the mock function for doOtherStuff and change it per test:
const myClass = require(`../index`);
jest.mock(`../myService`, () => {
const doOtherStuff = jest.fn(); // <= create a mock function for doOtherStuff
const result = { doOtherStuff };
return function() { return result; }; // <= always return the same object
});
const doOtherStuff = require('./myService')().doOtherStuff; // <= get doOtherStuff
describe(`service-tests - index`, () => {
test(`doStuff and test otherStuff`, async () => {
doOtherStuff.mockReturnValue(1); // <= mock it to return something
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe(1); // Success!
});
test(`doStuff and test otherStuff`, async () => {
doOtherStuff.mockReturnValue('something else'); // <= mock it to return something else
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe('something else'); // Success!
});
});
It also works to auto-mock myService.js and use mockImplementation...
...but since index.js creates a myService instance as soon as it runs you have to make sure the mock is in place before you require index.js:
jest.mock(`../myService`); // <= auto-mock
const myService = require('../myService');
const doOtherStuff = jest.fn();
myService.mockImplementation(function() { return { doOtherStuff }; });
const myClass = require(`../index`); // <= now require index
describe(`service-tests - index`, () => {
test(`doStuff and test otherStuff`, async () => {
doOtherStuff.mockReturnValue(1); // <= mock it to return something
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe(1); // Success!
});
test(`doStuff and test otherStuff`, async () => {
doOtherStuff.mockReturnValue('something else'); // <= mock it to return something else
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe('something else'); // Success!
});
});