Pārlūkot izejas kodu

feat(cli): enhance Cargo features injection, add tests (#7141)

Lucas Fernandes Nogueira 2 gadi atpakaļ
vecāks
revīzija
52474e479d

+ 5 - 0
.changes/build-enhance-features-check.md

@@ -0,0 +1,5 @@
+---
+"tauri-build": patch
+---
+
+Enhance Cargo features check.

+ 6 - 0
.changes/enhance-allowlist-injection.md

@@ -0,0 +1,6 @@
+---
+"tauri-cli": patch
+"@tauri-apps/cli": patch
+---
+
+Enhance injection of Cargo features.

+ 211 - 0
core/tauri-build/src/allowlist.rs

@@ -0,0 +1,211 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use anyhow::{anyhow, Result};
+use cargo_toml::{Dependency, Manifest};
+use tauri_utils::config::{Config, PatternKind, TauriConfig};
+
+#[derive(Debug, Default, PartialEq, Eq)]
+struct Diff {
+  remove: Vec<String>,
+  add: Vec<String>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum DependencyKind {
+  Build,
+  Normal,
+}
+
+#[derive(Debug)]
+struct AllowlistedDependency {
+  name: String,
+  alias: Option<String>,
+  kind: DependencyKind,
+  all_cli_managed_features: Option<Vec<&'static str>>,
+  expected_features: Vec<String>,
+}
+
+pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> {
+  let dependencies = vec![
+    AllowlistedDependency {
+      name: "tauri-build".into(),
+      alias: None,
+      kind: DependencyKind::Build,
+      all_cli_managed_features: Some(vec!["isolation"]),
+      expected_features: match config.tauri.pattern {
+        PatternKind::Isolation { .. } => vec!["isolation".to_string()],
+        _ => vec![],
+      },
+    },
+    AllowlistedDependency {
+      name: "tauri".into(),
+      alias: None,
+      kind: DependencyKind::Normal,
+      all_cli_managed_features: Some(TauriConfig::all_features()),
+      expected_features: config
+        .tauri
+        .features()
+        .into_iter()
+        .map(|f| f.to_string())
+        .collect::<Vec<String>>(),
+    },
+  ];
+
+  for metadata in dependencies {
+    let mut name = metadata.name.clone();
+    let mut deps = find_dependency(manifest, &metadata.name, metadata.kind);
+    if deps.is_empty() {
+      if let Some(alias) = &metadata.alias {
+        deps = find_dependency(manifest, alias, metadata.kind);
+        name = alias.clone();
+      }
+    }
+
+    for dep in deps {
+      if let Err(error) = check_features(dep, &metadata) {
+        return Err(anyhow!("
+      The `{}` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`.
+      Please run `tauri dev` or `tauri build` or {}.
+    ", name, error));
+      }
+    }
+  }
+
+  Ok(())
+}
+
+fn find_dependency(manifest: &mut Manifest, name: &str, kind: DependencyKind) -> Vec<Dependency> {
+  let dep = match kind {
+    DependencyKind::Build => manifest.build_dependencies.remove(name),
+    DependencyKind::Normal => manifest.dependencies.remove(name),
+  };
+
+  if let Some(dep) = dep {
+    vec![dep]
+  } else {
+    let mut deps = Vec::new();
+    for target in manifest.target.values_mut() {
+      if let Some(dep) = match kind {
+        DependencyKind::Build => target.build_dependencies.remove(name),
+        DependencyKind::Normal => target.dependencies.remove(name),
+      } {
+        deps.push(dep);
+      }
+    }
+    deps
+  }
+}
+
+fn features_diff(current: &[String], expected: &[String]) -> Diff {
+  let mut remove = Vec::new();
+  let mut add = Vec::new();
+  for feature in current {
+    if !expected.contains(feature) {
+      remove.push(feature.clone());
+    }
+  }
+
+  for feature in expected {
+    if !current.contains(feature) {
+      add.push(feature.clone());
+    }
+  }
+
+  Diff { remove, add }
+}
+
+fn check_features(dependency: Dependency, metadata: &AllowlistedDependency) -> Result<(), String> {
+  let features = match dependency {
+    Dependency::Simple(_) => Vec::new(),
+    Dependency::Detailed(dep) => dep.features,
+    Dependency::Inherited(dep) => dep.features,
+  };
+
+  let diff = if let Some(all_cli_managed_features) = &metadata.all_cli_managed_features {
+    features_diff(
+      &features
+        .into_iter()
+        .filter(|f| all_cli_managed_features.contains(&f.as_str()))
+        .collect::<Vec<String>>(),
+      &metadata.expected_features,
+    )
+  } else {
+    features_diff(
+      &features
+        .into_iter()
+        .filter(|f| f.starts_with("allow-"))
+        .collect::<Vec<String>>(),
+      &metadata.expected_features,
+    )
+  };
+
+  let mut error_message = String::new();
+  if !diff.remove.is_empty() {
+    error_message.push_str("remove the `");
+    error_message.push_str(&diff.remove.join(", "));
+    error_message.push_str(if diff.remove.len() == 1 {
+      "` feature"
+    } else {
+      "` features"
+    });
+    if !diff.add.is_empty() {
+      error_message.push_str(" and ");
+    }
+  }
+  if !diff.add.is_empty() {
+    error_message.push_str("add the `");
+    error_message.push_str(&diff.add.join(", "));
+    error_message.push_str(if diff.add.len() == 1 {
+      "` feature"
+    } else {
+      "` features"
+    });
+  }
+
+  if error_message.is_empty() {
+    Ok(())
+  } else {
+    Err(error_message)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use super::Diff;
+
+  #[test]
+  fn array_diff() {
+    for (current, expected, result) in [
+      (vec![], vec![], Default::default()),
+      (
+        vec!["a".into()],
+        vec![],
+        Diff {
+          remove: vec!["a".into()],
+          add: vec![],
+        },
+      ),
+      (vec!["a".into()], vec!["a".into()], Default::default()),
+      (
+        vec!["a".into(), "b".into()],
+        vec!["a".into()],
+        Diff {
+          remove: vec!["b".into()],
+          add: vec![],
+        },
+      ),
+      (
+        vec!["a".into(), "b".into()],
+        vec!["a".into(), "c".into()],
+        Diff {
+          remove: vec!["b".into()],
+          add: vec!["c".into()],
+        },
+      ),
+    ] {
+      assert_eq!(super::features_diff(&current, &expected), result);
+    }
+  }
+}

+ 3 - 148
core/tauri-build/src/lib.rs

@@ -5,7 +5,7 @@
 #![cfg_attr(doc_cfg, feature(doc_cfg))]
 
 pub use anyhow::Result;
-use cargo_toml::{Dependency, Manifest};
+use cargo_toml::Manifest;
 use heck::AsShoutySnakeCase;
 
 use tauri_utils::{
@@ -15,6 +15,7 @@ use tauri_utils::{
 
 use std::path::{Path, PathBuf};
 
+mod allowlist;
 #[cfg(feature = "codegen")]
 mod codegen;
 mod static_vcruntime;
@@ -320,27 +321,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
     Manifest::complete_from_path(&mut manifest, Path::new("Cargo.toml"))?;
   }
 
-  if let Some(tauri_build) = manifest.build_dependencies.remove("tauri-build") {
-    let error_message = check_features(&config, tauri_build, true);
-
-    if !error_message.is_empty() {
-      return Err(anyhow!("
-      The `tauri-build` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`.
-      Please run `tauri dev` or `tauri build` or {}.
-    ", error_message));
-    }
-  }
-
-  if let Some(tauri) = manifest.dependencies.remove("tauri") {
-    let error_message = check_features(&config, tauri, false);
-
-    if !error_message.is_empty() {
-      return Err(anyhow!("
-      The `tauri` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`.
-      Please run `tauri dev` or `tauri build` or {}.
-    ", error_message));
-    }
-  }
+  allowlist::check(&config, &mut manifest)?;
 
   let target_triple = std::env::var("TARGET").unwrap();
   let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
@@ -487,93 +468,6 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
   Ok(())
 }
 
-#[derive(Debug, Default, PartialEq, Eq)]
-struct Diff {
-  remove: Vec<String>,
-  add: Vec<String>,
-}
-
-fn features_diff(current: &[String], expected: &[String]) -> Diff {
-  let mut remove = Vec::new();
-  let mut add = Vec::new();
-  for feature in current {
-    if !expected.contains(feature) {
-      remove.push(feature.clone());
-    }
-  }
-
-  for feature in expected {
-    if !current.contains(feature) {
-      add.push(feature.clone());
-    }
-  }
-
-  Diff { remove, add }
-}
-
-fn check_features(config: &Config, dependency: Dependency, is_tauri_build: bool) -> String {
-  use tauri_utils::config::{PatternKind, TauriConfig};
-
-  let features = match dependency {
-    Dependency::Simple(_) => Vec::new(),
-    Dependency::Detailed(dep) => dep.features,
-    Dependency::Inherited(dep) => dep.features,
-  };
-
-  let all_cli_managed_features = if is_tauri_build {
-    vec!["isolation"]
-  } else {
-    TauriConfig::all_features()
-  };
-
-  let expected = if is_tauri_build {
-    match config.tauri.pattern {
-      PatternKind::Isolation { .. } => vec!["isolation".to_string()],
-      _ => vec![],
-    }
-  } else {
-    config
-      .tauri
-      .features()
-      .into_iter()
-      .map(|f| f.to_string())
-      .collect::<Vec<String>>()
-  };
-
-  let diff = features_diff(
-    &features
-      .into_iter()
-      .filter(|f| all_cli_managed_features.contains(&f.as_str()))
-      .collect::<Vec<String>>(),
-    &expected,
-  );
-
-  let mut error_message = String::new();
-  if !diff.remove.is_empty() {
-    error_message.push_str("remove the `");
-    error_message.push_str(&diff.remove.join(", "));
-    error_message.push_str(if diff.remove.len() == 1 {
-      "` feature"
-    } else {
-      "` features"
-    });
-    if !diff.add.is_empty() {
-      error_message.push_str(" and ");
-    }
-  }
-  if !diff.add.is_empty() {
-    error_message.push_str("add the `");
-    error_message.push_str(&diff.add.join(", "));
-    error_message.push_str(if diff.add.len() == 1 {
-      "` feature"
-    } else {
-      "` features"
-    });
-  }
-
-  error_message
-}
-
 #[derive(serde::Deserialize)]
 struct CargoMetadata {
   workspace_root: PathBuf,
@@ -593,42 +487,3 @@ fn get_workspace_dir() -> Result<PathBuf> {
 
   Ok(serde_json::from_slice::<CargoMetadata>(&output.stdout)?.workspace_root)
 }
-
-#[cfg(test)]
-mod tests {
-  use super::Diff;
-
-  #[test]
-  fn array_diff() {
-    for (current, expected, result) in [
-      (vec![], vec![], Default::default()),
-      (
-        vec!["a".into()],
-        vec![],
-        Diff {
-          remove: vec!["a".into()],
-          add: vec![],
-        },
-      ),
-      (vec!["a".into()], vec!["a".into()], Default::default()),
-      (
-        vec!["a".into(), "b".into()],
-        vec!["a".into()],
-        Diff {
-          remove: vec!["b".into()],
-          add: vec![],
-        },
-      ),
-      (
-        vec!["a".into(), "b".into()],
-        vec!["a".into(), "c".into()],
-        Diff {
-          remove: vec!["b".into()],
-          add: vec!["c".into()],
-        },
-      ),
-    ] {
-      assert_eq!(super::features_diff(&current, &expected), result);
-    }
-  }
-}

+ 0 - 2
core/tauri-utils/src/config.rs

@@ -2434,7 +2434,6 @@ pub struct TauriConfig {
 
 impl TauriConfig {
   /// Returns all Cargo features.
-  #[allow(dead_code)]
   pub fn all_features() -> Vec<&'static str> {
     let mut features = AllowlistConfig::all_features();
     features.extend(vec![
@@ -2448,7 +2447,6 @@ impl TauriConfig {
   }
 
   /// Returns the enabled Cargo features.
-  #[allow(dead_code)]
   pub fn features(&self) -> Vec<&str> {
     let mut features = self.allowlist.to_features();
     if self.cli.is_some() {

+ 317 - 75
tooling/cli/src/interface/rust/manifest.rs

@@ -10,7 +10,7 @@ use crate::helpers::{
 use anyhow::Context;
 use itertools::Itertools;
 use log::info;
-use toml_edit::{Array, Document, InlineTable, Item, Table, TableLike, Value};
+use toml_edit::{Array, Document, InlineTable, Item, TableLike, Value};
 
 use std::{
   collections::{HashMap, HashSet},
@@ -108,31 +108,55 @@ fn toml_array(features: &HashSet<String>) -> Array {
   f
 }
 
-fn write_features(
-  dependencies: &mut Table,
+fn find_dependency<'a>(
+  manifest: &'a mut Document,
+  name: &'a str,
+  kind: DependencyKind,
+) -> Vec<&'a mut Item> {
+  let table = match kind {
+    DependencyKind::Build => "build-dependencies",
+    DependencyKind::Normal => "dependencies",
+  };
+
+  let m = manifest.as_table_mut();
+  for (k, v) in m.iter_mut() {
+    if let Some(t) = v.as_table_mut() {
+      if k == table {
+        if let Some(item) = t.get_mut(name) {
+          return vec![item];
+        }
+      } else if k == "target" {
+        let mut matching_deps = Vec::new();
+        for (_, target_value) in t.iter_mut() {
+          if let Some(target_table) = target_value.as_table_mut() {
+            if let Some(deps) = target_table.get_mut(table) {
+              if let Some(item) = deps.as_table_mut().and_then(|t| t.get_mut(name)) {
+                matching_deps.push(item);
+              }
+            }
+          }
+        }
+        return matching_deps;
+      }
+    }
+  }
+
+  Vec::new()
+}
+
+fn write_features<F: Fn(&str) -> bool>(
   dependency_name: &str,
-  all_features: Vec<&str>,
+  item: &mut Item,
+  is_managed_feature: F,
   features: &mut HashSet<String>,
 ) -> crate::Result<bool> {
-  let item = dependencies.entry(dependency_name).or_insert(Item::None);
-
-  // do not rewrite if dependency uses workspace inheritance
-  if item
-    .get("workspace")
-    .and_then(|v| v.as_bool())
-    .unwrap_or_default()
-  {
-    info!("`{dependency_name}` dependency has workspace inheritance enabled. The features array won't be automatically rewritten. Expected features: [{}]", features.iter().join(", "));
-    return Ok(false);
-  }
-
   if let Some(dep) = item.as_table_mut() {
-    inject_features_table(dep, &all_features, features);
+    inject_features_table(dep, is_managed_feature, features);
     Ok(true)
   } else if let Some(dep) = item.as_value_mut() {
     match dep {
       Value::InlineTable(table) => {
-        inject_features_table(table, &all_features, features);
+        inject_features_table(table, is_managed_feature, features);
       }
       Value::String(version) => {
         let mut def = InlineTable::default();
@@ -153,16 +177,30 @@ fn write_features(
   }
 }
 
-fn inject_features_table<D: TableLike>(
+#[derive(Debug, Clone, Copy)]
+enum DependencyKind {
+  Build,
+  Normal,
+}
+
+#[derive(Debug)]
+struct DependencyAllowlist {
+  name: String,
+  kind: DependencyKind,
+  all_cli_managed_features: Vec<&'static str>,
+  features: HashSet<String>,
+}
+
+fn inject_features_table<D: TableLike, F: Fn(&str) -> bool>(
   dep: &mut D,
-  all_features: &[&str],
+  is_managed_feature: F,
   features: &mut HashSet<String>,
 ) {
   let manifest_features = dep.entry("features").or_insert(Item::None);
   if let Item::Value(Value::Array(f)) = &manifest_features {
     for feat in f.iter() {
       if let Value::String(feature) = feat {
-        if !all_features.contains(&feature.value().as_str()) {
+        if !is_managed_feature(feature.value().as_str()) {
           features.insert(feature.value().to_string());
         }
       }
@@ -192,71 +230,275 @@ fn inject_features_table<D: TableLike>(
   }
 }
 
+fn inject_features(
+  manifest: &mut Document,
+  dependencies: &mut Vec<DependencyAllowlist>,
+) -> crate::Result<bool> {
+  let mut persist = false;
+  for dependency in dependencies {
+    let name = dependency.name.clone();
+    let items = find_dependency(manifest, &dependency.name, dependency.kind);
+
+    for item in items {
+      // do not rewrite if dependency uses workspace inheritance
+      if item
+        .get("workspace")
+        .and_then(|v| v.as_bool())
+        .unwrap_or_default()
+      {
+        info!("`{name}` dependency has workspace inheritance enabled. The features array won't be automatically rewritten. Expected features: [{}]", dependency.features.iter().join(", "));
+      } else {
+        let all_cli_managed_features = dependency.all_cli_managed_features.clone();
+        let is_managed_feature: Box<dyn Fn(&str) -> bool> =
+          Box::new(move |feature| all_cli_managed_features.contains(&feature));
+
+        let should_write =
+          write_features(&name, item, is_managed_feature, &mut dependency.features)?;
+
+        if !persist {
+          persist = should_write;
+        }
+      }
+    }
+  }
+
+  Ok(persist)
+}
+
 pub fn rewrite_manifest(config: &Config) -> crate::Result<Manifest> {
   let manifest_path = tauri_dir().join("Cargo.toml");
   let mut manifest = read_manifest(&manifest_path)?;
 
+  let mut dependencies = Vec::new();
+
+  // tauri-build
   let mut tauri_build_features = HashSet::new();
   if let PatternKind::Isolation { .. } = config.tauri.pattern {
     tauri_build_features.insert("isolation".to_string());
   }
-  let resp = write_features(
-    manifest
-      .as_table_mut()
-      .entry("build-dependencies")
-      .or_insert(Item::Table(Table::new()))
-      .as_table_mut()
-      .expect("manifest build-dependencies isn't a table"),
-    "tauri-build",
-    vec!["isolation"],
-    &mut tauri_build_features,
-  )?;
-
-  let mut tauri_features =
+  dependencies.push(DependencyAllowlist {
+    name: "tauri-build".into(),
+    kind: DependencyKind::Build,
+    all_cli_managed_features: vec!["isolation"],
+    features: tauri_build_features,
+  });
+
+  // tauri
+  let tauri_features =
     HashSet::from_iter(config.tauri.features().into_iter().map(|f| f.to_string()));
-  let cli_managed_tauri_features = crate::helpers::config::TauriConfig::all_features();
-  let res = match write_features(
-    manifest
-      .as_table_mut()
-      .entry("dependencies")
-      .or_insert(Item::Table(Table::new()))
-      .as_table_mut()
-      .expect("manifest dependencies isn't a table"),
-    "tauri",
-    cli_managed_tauri_features,
-    &mut tauri_features,
-  ) {
-    Err(e) => Err(e),
-    Ok(t) if !resp => Ok(t),
-    _ => Ok(true),
-  };
+  dependencies.push(DependencyAllowlist {
+    name: "tauri".into(),
+    kind: DependencyKind::Normal,
+    all_cli_managed_features: crate::helpers::config::TauriConfig::all_features(),
+    features: tauri_features,
+  });
 
-  match res {
-    Ok(true) => {
-      let mut manifest_file =
-        File::create(&manifest_path).with_context(|| "failed to open Cargo.toml for rewrite")?;
-      manifest_file.write_all(
-        manifest
-          .to_string()
-          // apply some formatting fixes
-          .replace(r#"" ,features =["#, r#"", features = ["#)
-          .replace(r#"" , features"#, r#"", features"#)
-          .replace("]}", "] }")
-          .replace("={", "= {")
-          .replace("=[", "= [")
-          .replace(r#"",""#, r#"", ""#)
-          .as_bytes(),
-      )?;
-      manifest_file.flush()?;
-      Ok(Manifest {
-        inner: manifest,
-        tauri_features,
-      })
-    }
-    Ok(false) => Ok(Manifest {
+  let persist = inject_features(&mut manifest, &mut dependencies)?;
+
+  let tauri_features = dependencies
+    .into_iter()
+    .find(|d| d.name == "tauri")
+    .unwrap()
+    .features;
+
+  if persist {
+    let mut manifest_file =
+      File::create(&manifest_path).with_context(|| "failed to open Cargo.toml for rewrite")?;
+    manifest_file.write_all(
+      manifest
+        .to_string()
+        // apply some formatting fixes
+        .replace(r#"" ,features =["#, r#"", features = ["#)
+        .replace(r#"" , features"#, r#"", features"#)
+        .replace("]}", "] }")
+        .replace("={", "= {")
+        .replace("=[", "= [")
+        .replace(r#"",""#, r#"", ""#)
+        .as_bytes(),
+    )?;
+    manifest_file.flush()?;
+    Ok(Manifest {
+      inner: manifest,
+      tauri_features,
+    })
+  } else {
+    Ok(Manifest {
       inner: manifest,
       tauri_features,
-    }),
-    Err(e) => Err(e),
+    })
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use super::{DependencyAllowlist, DependencyKind};
+  use std::collections::{HashMap, HashSet};
+
+  fn inject_features(toml: &str, mut dependencies: Vec<DependencyAllowlist>) {
+    let mut manifest = toml.parse::<toml_edit::Document>().expect("invalid toml");
+
+    let mut expected = HashMap::new();
+    for dep in &dependencies {
+      let mut features = dep.features.clone();
+      for item in super::find_dependency(&mut manifest, &dep.name, dep.kind) {
+        let item_table = if let Some(table) = item.as_table() {
+          Some(table.clone())
+        } else if let Some(toml_edit::Value::InlineTable(table)) = item.as_value() {
+          Some(table.clone().into_table())
+        } else {
+          None
+        };
+        if let Some(f) = item_table
+          .and_then(|t| t.get("features").cloned())
+          .and_then(|f| f.as_array().cloned())
+        {
+          for feature in f.iter() {
+            let feature = feature.as_str().expect("feature is not a string");
+            if !dep.all_cli_managed_features.contains(&feature) {
+              features.insert(feature.into());
+            }
+          }
+        }
+      }
+      expected.insert(dep.name.clone(), features);
+    }
+
+    super::inject_features(&mut manifest, &mut dependencies).expect("failed to migrate manifest");
+
+    for dep in dependencies {
+      let expected_features = expected.get(&dep.name).unwrap();
+      for item in super::find_dependency(&mut manifest, &dep.name, dep.kind) {
+        let item_table = if let Some(table) = item.as_table() {
+          table.clone()
+        } else if let Some(toml_edit::Value::InlineTable(table)) = item.as_value() {
+          table.clone().into_table()
+        } else {
+          panic!("unexpected TOML item kind for {}", dep.name);
+        };
+
+        let features_array = item_table
+          .get("features")
+          .expect("missing features")
+          .as_array()
+          .expect("features must be an array")
+          .clone();
+
+        let mut features = Vec::new();
+        for feature in features_array.iter() {
+          let feature = feature.as_str().expect("feature must be a string");
+          features.push(feature);
+        }
+        for expected in expected_features {
+          assert!(
+            features.contains(&expected.as_str()),
+            "feature {expected} should have been injected"
+          );
+        }
+      }
+    }
+  }
+
+  fn tauri_dependency(features: HashSet<String>) -> DependencyAllowlist {
+    DependencyAllowlist {
+      name: "tauri".into(),
+      kind: DependencyKind::Normal,
+      all_cli_managed_features: vec!["isolation"],
+      features,
+    }
+  }
+
+  fn tauri_build_dependency(features: HashSet<String>) -> DependencyAllowlist {
+    DependencyAllowlist {
+      name: "tauri-build".into(),
+      kind: DependencyKind::Build,
+      all_cli_managed_features: crate::helpers::config::TauriConfig::all_features(),
+      features,
+    }
+  }
+
+  #[test]
+  fn inject_features_table() {
+    inject_features(
+      r#"
+    [dependencies]
+    tauri = { version = "1", features = ["dummy"] }
+
+    [build-dependencies]
+    tauri-build = { version = "1" }
+"#,
+      vec![
+        tauri_dependency(HashSet::from_iter(
+          crate::helpers::config::TauriConfig::all_features()
+            .iter()
+            .map(|f| f.to_string()),
+        )),
+        tauri_build_dependency(HashSet::from_iter(vec!["isolation".into()])),
+      ],
+    );
+  }
+
+  #[test]
+  fn inject_features_target() {
+    inject_features(
+      r#"
+    [target."cfg(windows)".dependencies]
+    tauri = { version = "1", features = ["dummy"] }
+
+    [target."cfg(target_os = \"macos\")".build-dependencies]
+    tauri-build = { version = "1" }
+
+    [target."cfg(target_os = \"linux\")".dependencies]
+    tauri = { version = "1", features = ["isolation"] }
+
+    [target."cfg(windows)".build-dependencies]
+    tauri-build = { version = "1" }
+"#,
+      vec![
+        tauri_dependency(Default::default()),
+        tauri_build_dependency(HashSet::from_iter(vec!["isolation".into()])),
+      ],
+    );
+  }
+
+  #[test]
+  fn inject_features_inline_table() {
+    inject_features(
+      r#"
+    [dependencies.tauri]
+    version = "1"
+    features = ["test"]
+
+    [build-dependencies.tauri-build]
+    version = "1"
+    features = ["config-toml", "codegen", "isolation"]
+"#,
+      vec![
+        tauri_dependency(HashSet::from_iter(vec![
+          "isolation".into(),
+          "native-tls-vendored".into(),
+        ])),
+        tauri_build_dependency(HashSet::from_iter(vec!["isolation".into()])),
+      ],
+    );
+  }
+
+  #[test]
+  fn inject_features_string() {
+    inject_features(
+      r#"
+    [dependencies]
+    tauri = "1"
+
+    [build-dependencies]
+    tauri-build = "1"
+"#,
+      vec![
+        tauri_dependency(HashSet::from_iter(vec![
+          "isolation".into(),
+          "native-tls-vendored".into(),
+        ])),
+        tauri_build_dependency(HashSet::from_iter(vec!["isolation".into()])),
+      ],
+    );
   }
 }