github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonformat/computed/renderers/renderer_test.go (about)

     1  package renderers
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	"github.com/mitchellh/colorstring"
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	"github.com/hashicorp/terraform/internal/plans"
    14  )
    15  
    16  func TestRenderers_Human(t *testing.T) {
    17  	colorize := colorstring.Colorize{
    18  		Colors:  colorstring.DefaultColors,
    19  		Disable: true,
    20  	}
    21  
    22  	tcs := map[string]struct {
    23  		diff     computed.Diff
    24  		expected string
    25  		opts     computed.RenderHumanOpts
    26  	}{
    27  		"primitive_create": {
    28  			diff: computed.Diff{
    29  				Renderer: Primitive(nil, 1.0, cty.Number),
    30  				Action:   plans.Create,
    31  			},
    32  			expected: "1",
    33  		},
    34  		"primitive_delete": {
    35  			diff: computed.Diff{
    36  				Renderer: Primitive(1.0, nil, cty.Number),
    37  				Action:   plans.Delete,
    38  			},
    39  			expected: "1 -> null",
    40  		},
    41  		"primitive_delete_override": {
    42  			diff: computed.Diff{
    43  				Renderer: Primitive(1.0, nil, cty.Number),
    44  				Action:   plans.Delete,
    45  			},
    46  			opts:     computed.RenderHumanOpts{OverrideNullSuffix: true},
    47  			expected: "1",
    48  		},
    49  		"primitive_update_to_null": {
    50  			diff: computed.Diff{
    51  				Renderer: Primitive(1.0, nil, cty.Number),
    52  				Action:   plans.Update,
    53  			},
    54  			expected: "1 -> null",
    55  		},
    56  		"primitive_update_from_null": {
    57  			diff: computed.Diff{
    58  				Renderer: Primitive(nil, 1.0, cty.Number),
    59  				Action:   plans.Update,
    60  			},
    61  			expected: "null -> 1",
    62  		},
    63  		"primitive_update": {
    64  			diff: computed.Diff{
    65  				Renderer: Primitive(0.0, 1.0, cty.Number),
    66  				Action:   plans.Update,
    67  			},
    68  			expected: "0 -> 1",
    69  		},
    70  		"primitive_update_long_float": {
    71  			diff: computed.Diff{
    72  				Renderer: Primitive(123456789.0, 987654321.0, cty.Number),
    73  				Action:   plans.Update,
    74  			},
    75  			expected: "123456789 -> 987654321",
    76  		},
    77  		"primitive_update_replace": {
    78  			diff: computed.Diff{
    79  				Renderer: Primitive(0.0, 1.0, cty.Number),
    80  				Action:   plans.Update,
    81  				Replace:  true,
    82  			},
    83  			expected: "0 -> 1 # forces replacement",
    84  		},
    85  		"primitive_multiline_string_create": {
    86  			diff: computed.Diff{
    87  				Renderer: Primitive(nil, "hello\nworld", cty.String),
    88  				Action:   plans.Create,
    89  			},
    90  			expected: `
    91  <<-EOT
    92          hello
    93          world
    94      EOT
    95  `,
    96  		},
    97  		"primitive_multiline_string_delete": {
    98  			diff: computed.Diff{
    99  				Renderer: Primitive("hello\nworld", nil, cty.String),
   100  				Action:   plans.Delete,
   101  			},
   102  			expected: `
   103  <<-EOT
   104          hello
   105          world
   106      EOT -> null
   107  `,
   108  		},
   109  		"primitive_multiline_string_update": {
   110  			diff: computed.Diff{
   111  				Renderer: Primitive("hello\nold\nworld", "hello\nnew\nworld", cty.String),
   112  				Action:   plans.Update,
   113  			},
   114  			expected: `
   115  <<-EOT
   116          hello
   117        - old
   118        + new
   119          world
   120      EOT
   121  `,
   122  		},
   123  		"primitive_json_string_create": {
   124  			diff: computed.Diff{
   125  				Renderer: Primitive(nil, "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   126  				Action:   plans.Create,
   127  			},
   128  			expected: `
   129  jsonencode(
   130          {
   131            + key_one = "value_one"
   132            + key_two = "value_two"
   133          }
   134      )
   135  `,
   136  		},
   137  		"primitive_json_string_delete": {
   138  			diff: computed.Diff{
   139  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", nil, cty.String),
   140  				Action:   plans.Delete,
   141  			},
   142  			expected: `
   143  jsonencode(
   144          {
   145            - key_one = "value_one"
   146            - key_two = "value_two"
   147          }
   148      ) -> null
   149  `,
   150  		},
   151  		"primitive_json_string_update": {
   152  			diff: computed.Diff{
   153  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\",\"key_three\":\"value_three\"}", cty.String),
   154  				Action:   plans.Update,
   155  			},
   156  			expected: `
   157  jsonencode(
   158        ~ {
   159            + key_three = "value_three"
   160              # (2 unchanged attributes hidden)
   161          }
   162      )
   163  `,
   164  		},
   165  		"primitive_json_explicit_nulls": {
   166  			diff: computed.Diff{
   167  				Renderer: Primitive("{\"key_one\":\"value_one\",\"key_two\":\"value_two\"}", "{\"key_one\":null}", cty.String),
   168  				Action:   plans.Update,
   169  			},
   170  			expected: `
   171  jsonencode(
   172        ~ {
   173            ~ key_one = "value_one" -> null
   174            - key_two = "value_two"
   175          }
   176      )
   177  `,
   178  		},
   179  		"primitive_fake_json_string_update": {
   180  			diff: computed.Diff{
   181  				// This isn't valid JSON, our renderer should be okay with it.
   182  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\",\"key_three\":\"value_three\"", cty.String),
   183  				Action:   plans.Update,
   184  			},
   185  			expected: "\"{\\\"key_one\\\": \\\"value_one\\\",\\\"key_two\\\":\\\"value_two\\\"\" -> \"{\\\"key_one\\\": \\\"value_one\\\",\\\"key_two\\\":\\\"value_two\\\",\\\"key_three\\\":\\\"value_three\\\"\"",
   186  		},
   187  		"primitive_multiline_to_json_update": {
   188  			diff: computed.Diff{
   189  				Renderer: Primitive("hello\nworld", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   190  				Action:   plans.Update,
   191  			},
   192  			expected: `
   193  <<-EOT
   194          hello
   195          world
   196      EOT -> jsonencode(
   197          {
   198            + key_one = "value_one"
   199            + key_two = "value_two"
   200          }
   201      )
   202  `,
   203  		},
   204  		"primitive_json_to_multiline_update": {
   205  			diff: computed.Diff{
   206  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "hello\nworld", cty.String),
   207  				Action:   plans.Update,
   208  			},
   209  			expected: `
   210  jsonencode(
   211          {
   212            - key_one = "value_one"
   213            - key_two = "value_two"
   214          }
   215      ) -> <<-EOT
   216          hello
   217          world
   218      EOT
   219  `,
   220  		},
   221  		"primitive_json_to_string_update": {
   222  			diff: computed.Diff{
   223  				Renderer: Primitive("{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", "hello world", cty.String),
   224  				Action:   plans.Update,
   225  			},
   226  			expected: `
   227  jsonencode(
   228          {
   229            - key_one = "value_one"
   230            - key_two = "value_two"
   231          }
   232      ) -> "hello world"
   233  `,
   234  		},
   235  		"primitive_string_to_json_update": {
   236  			diff: computed.Diff{
   237  				Renderer: Primitive("hello world", "{\"key_one\": \"value_one\",\"key_two\":\"value_two\"}", cty.String),
   238  				Action:   plans.Update,
   239  			},
   240  			expected: `
   241  "hello world" -> jsonencode(
   242          {
   243            + key_one = "value_one"
   244            + key_two = "value_two"
   245          }
   246      )
   247  `,
   248  		},
   249  		"primitive_multi_to_single_update": {
   250  			diff: computed.Diff{
   251  				Renderer: Primitive("hello\nworld", "hello world", cty.String),
   252  				Action:   plans.Update,
   253  			},
   254  			expected: `
   255  <<-EOT
   256        - hello
   257        - world
   258        + hello world
   259      EOT
   260  `,
   261  		},
   262  		"primitive_single_to_multi_update": {
   263  			diff: computed.Diff{
   264  				Renderer: Primitive("hello world", "hello\nworld", cty.String),
   265  				Action:   plans.Update,
   266  			},
   267  			expected: `
   268  <<-EOT
   269        - hello world
   270        + hello
   271        + world
   272      EOT
   273  `,
   274  		},
   275  		"sensitive_update": {
   276  			diff: computed.Diff{
   277  				Renderer: Sensitive(computed.Diff{
   278  					Renderer: Primitive(0.0, 1.0, cty.Number),
   279  					Action:   plans.Update,
   280  				}, true, true),
   281  				Action: plans.Update,
   282  			},
   283  			expected: "(sensitive value)",
   284  		},
   285  		"sensitive_update_replace": {
   286  			diff: computed.Diff{
   287  				Renderer: Sensitive(computed.Diff{
   288  					Renderer: Primitive(0.0, 1.0, cty.Number),
   289  					Action:   plans.Update,
   290  					Replace:  true,
   291  				}, true, true),
   292  				Action:  plans.Update,
   293  				Replace: true,
   294  			},
   295  			expected: "(sensitive value) # forces replacement",
   296  		},
   297  		"computed_create": {
   298  			diff: computed.Diff{
   299  				Renderer: Unknown(computed.Diff{}),
   300  				Action:   plans.Create,
   301  			},
   302  			expected: "(known after apply)",
   303  		},
   304  		"computed_update": {
   305  			diff: computed.Diff{
   306  				Renderer: Unknown(computed.Diff{
   307  					Renderer: Primitive(0.0, nil, cty.Number),
   308  					Action:   plans.Delete,
   309  				}),
   310  				Action: plans.Update,
   311  			},
   312  			expected: "0 -> (known after apply)",
   313  		},
   314  		"object_created": {
   315  			diff: computed.Diff{
   316  				Renderer: Object(map[string]computed.Diff{}),
   317  				Action:   plans.Create,
   318  			},
   319  			expected: "{}",
   320  		},
   321  		"object_created_with_attributes": {
   322  			diff: computed.Diff{
   323  				Renderer: Object(map[string]computed.Diff{
   324  					"attribute_one": {
   325  						Renderer: Primitive(nil, 0.0, cty.Number),
   326  						Action:   plans.Create,
   327  					},
   328  				}),
   329  				Action: plans.Create,
   330  			},
   331  			expected: `
   332  {
   333        + attribute_one = 0
   334      }
   335  `,
   336  		},
   337  		"object_deleted": {
   338  			diff: computed.Diff{
   339  				Renderer: Object(map[string]computed.Diff{}),
   340  				Action:   plans.Delete,
   341  			},
   342  			expected: "{} -> null",
   343  		},
   344  		"object_deleted_with_attributes": {
   345  			diff: computed.Diff{
   346  				Renderer: Object(map[string]computed.Diff{
   347  					"attribute_one": {
   348  						Renderer: Primitive(0.0, nil, cty.Number),
   349  						Action:   plans.Delete,
   350  					},
   351  				}),
   352  				Action: plans.Delete,
   353  			},
   354  			expected: `
   355  {
   356        - attribute_one = 0
   357      } -> null
   358  `,
   359  		},
   360  		"nested_object_deleted": {
   361  			diff: computed.Diff{
   362  				Renderer: NestedObject(map[string]computed.Diff{}),
   363  				Action:   plans.Delete,
   364  			},
   365  			expected: "{} -> null",
   366  		},
   367  		"nested_object_deleted_with_attributes": {
   368  			diff: computed.Diff{
   369  				Renderer: NestedObject(map[string]computed.Diff{
   370  					"attribute_one": {
   371  						Renderer: Primitive(0.0, nil, cty.Number),
   372  						Action:   plans.Delete,
   373  					},
   374  				}),
   375  				Action: plans.Delete,
   376  			},
   377  			expected: `
   378  {
   379        - attribute_one = 0 -> null
   380      } -> null
   381  `,
   382  		},
   383  		"object_create_attribute": {
   384  			diff: computed.Diff{
   385  				Renderer: Object(map[string]computed.Diff{
   386  					"attribute_one": {
   387  						Renderer: Primitive(nil, 0.0, cty.Number),
   388  						Action:   plans.Create,
   389  					},
   390  				}),
   391  				Action: plans.Update,
   392  			},
   393  			expected: `
   394  {
   395        + attribute_one = 0
   396      }
   397  `,
   398  		},
   399  		"object_update_attribute": {
   400  			diff: computed.Diff{
   401  				Renderer: Object(map[string]computed.Diff{
   402  					"attribute_one": {
   403  						Renderer: Primitive(0.0, 1.0, cty.Number),
   404  						Action:   plans.Update,
   405  					},
   406  				}),
   407  				Action: plans.Update,
   408  			},
   409  			expected: `
   410  {
   411        ~ attribute_one = 0 -> 1
   412      }
   413  `,
   414  		},
   415  		"object_update_attribute_forces_replacement": {
   416  			diff: computed.Diff{
   417  				Renderer: Object(map[string]computed.Diff{
   418  					"attribute_one": {
   419  						Renderer: Primitive(0.0, 1.0, cty.Number),
   420  						Action:   plans.Update,
   421  					},
   422  				}),
   423  				Action:  plans.Update,
   424  				Replace: true,
   425  			},
   426  			expected: `
   427  { # forces replacement
   428        ~ attribute_one = 0 -> 1
   429      }
   430  `,
   431  		},
   432  		"object_delete_attribute": {
   433  			diff: computed.Diff{
   434  				Renderer: Object(map[string]computed.Diff{
   435  					"attribute_one": {
   436  						Renderer: Primitive(0.0, nil, cty.Number),
   437  						Action:   plans.Delete,
   438  					},
   439  				}),
   440  				Action: plans.Update,
   441  			},
   442  			expected: `
   443  {
   444        - attribute_one = 0
   445      }
   446  `,
   447  		},
   448  		"object_ignore_unchanged_attributes": {
   449  			diff: computed.Diff{
   450  				Renderer: Object(map[string]computed.Diff{
   451  					"attribute_one": {
   452  						Renderer: Primitive(0.0, 1.0, cty.Number),
   453  						Action:   plans.Update,
   454  					},
   455  					"attribute_two": {
   456  						Renderer: Primitive(0.0, 0.0, cty.Number),
   457  						Action:   plans.NoOp,
   458  					},
   459  					"attribute_three": {
   460  						Renderer: Primitive(nil, 1.0, cty.Number),
   461  						Action:   plans.Create,
   462  					},
   463  				}),
   464  				Action: plans.Update,
   465  			},
   466  			expected: `
   467  {
   468        ~ attribute_one   = 0 -> 1
   469        + attribute_three = 1
   470          # (1 unchanged attribute hidden)
   471      }
   472  `,
   473  		},
   474  		"object_create_sensitive_attribute": {
   475  			diff: computed.Diff{
   476  				Renderer: Object(map[string]computed.Diff{
   477  					"attribute_one": {
   478  						Renderer: Sensitive(computed.Diff{
   479  							Renderer: Primitive(nil, 1.0, cty.Number),
   480  							Action:   plans.Create,
   481  						}, false, true),
   482  						Action: plans.Create,
   483  					},
   484  				}),
   485  				Action: plans.Update,
   486  			},
   487  			expected: `
   488  {
   489        + attribute_one = (sensitive value)
   490      }
   491  `,
   492  		},
   493  		"object_update_sensitive_attribute": {
   494  			diff: computed.Diff{
   495  				Renderer: Object(map[string]computed.Diff{
   496  					"attribute_one": {
   497  						Renderer: Sensitive(computed.Diff{
   498  							Renderer: Primitive(0.0, 1.0, cty.Number),
   499  							Action:   plans.Update,
   500  						}, true, true),
   501  						Action: plans.Update,
   502  					},
   503  				}),
   504  				Action: plans.Update,
   505  			},
   506  			expected: `
   507  {
   508        ~ attribute_one = (sensitive value)
   509      }
   510  `,
   511  		},
   512  		"object_delete_sensitive_attribute": {
   513  			diff: computed.Diff{
   514  				Renderer: Object(map[string]computed.Diff{
   515  					"attribute_one": {
   516  						Renderer: Sensitive(computed.Diff{
   517  							Renderer: Primitive(0.0, nil, cty.Number),
   518  							Action:   plans.Delete,
   519  						}, true, false),
   520  						Action: plans.Delete,
   521  					},
   522  				}),
   523  				Action: plans.Update,
   524  			},
   525  			expected: `
   526  {
   527        - attribute_one = (sensitive value)
   528      }
   529  `,
   530  		},
   531  		"object_create_computed_attribute": {
   532  			diff: computed.Diff{
   533  				Renderer: Object(map[string]computed.Diff{
   534  					"attribute_one": {
   535  						Renderer: Unknown(computed.Diff{Renderer: nil}),
   536  						Action:   plans.Create,
   537  					},
   538  				}),
   539  				Action: plans.Update,
   540  			},
   541  			expected: `
   542  {
   543        + attribute_one = (known after apply)
   544      }
   545  `,
   546  		},
   547  		"object_update_computed_attribute": {
   548  			diff: computed.Diff{
   549  				Renderer: Object(map[string]computed.Diff{
   550  					"attribute_one": {
   551  						Renderer: Unknown(computed.Diff{
   552  							Renderer: Primitive(1.0, nil, cty.Number),
   553  							Action:   plans.Delete,
   554  						}),
   555  						Action: plans.Update,
   556  					},
   557  				}),
   558  				Action: plans.Update,
   559  			},
   560  			expected: `
   561  {
   562        ~ attribute_one = 1 -> (known after apply)
   563      }
   564  `,
   565  		},
   566  		"object_escapes_attribute_keys": {
   567  			diff: computed.Diff{
   568  				Renderer: Object(map[string]computed.Diff{
   569  					"attribute_one": {
   570  						Renderer: Primitive(1.0, 2.0, cty.Number),
   571  						Action:   plans.Update,
   572  					},
   573  					"attribute:two": {
   574  						Renderer: Primitive(2.0, 3.0, cty.Number),
   575  						Action:   plans.Update,
   576  					},
   577  					"attribute_six": {
   578  						Renderer: Primitive(3.0, 4.0, cty.Number),
   579  						Action:   plans.Update,
   580  					},
   581  				}),
   582  				Action: plans.Update,
   583  			},
   584  			expected: `
   585  {
   586        ~ "attribute:two" = 2 -> 3
   587        ~ attribute_one   = 1 -> 2
   588        ~ attribute_six   = 3 -> 4
   589      }
   590  `,
   591  		},
   592  		"map_create_empty": {
   593  			diff: computed.Diff{
   594  				Renderer: Map(map[string]computed.Diff{}),
   595  				Action:   plans.Create,
   596  			},
   597  			expected: "{}",
   598  		},
   599  		"map_create": {
   600  			diff: computed.Diff{
   601  				Renderer: Map(map[string]computed.Diff{
   602  					"element_one": {
   603  						Renderer: Primitive(nil, "new", cty.String),
   604  						Action:   plans.Create,
   605  					},
   606  				}),
   607  				Action: plans.Create,
   608  			},
   609  			expected: `
   610  {
   611        + "element_one" = "new"
   612      }
   613  `,
   614  		},
   615  		"map_delete_empty": {
   616  			diff: computed.Diff{
   617  				Renderer: Map(map[string]computed.Diff{}),
   618  				Action:   plans.Delete,
   619  			},
   620  			expected: "{} -> null",
   621  		},
   622  		"map_delete": {
   623  			diff: computed.Diff{
   624  				Renderer: Map(map[string]computed.Diff{
   625  					"element_one": {
   626  						Renderer: Primitive("old", nil, cty.String),
   627  						Action:   plans.Delete,
   628  					},
   629  				}),
   630  				Action: plans.Delete,
   631  			},
   632  			expected: `
   633  {
   634        - "element_one" = "old"
   635      } -> null
   636  `,
   637  		},
   638  		"map_create_element": {
   639  			diff: computed.Diff{
   640  				Renderer: Map(map[string]computed.Diff{
   641  					"element_one": {
   642  						Renderer: Primitive(nil, "new", cty.String),
   643  						Action:   plans.Create,
   644  					},
   645  				}),
   646  				Action: plans.Update,
   647  			},
   648  			expected: `
   649  {
   650        + "element_one" = "new"
   651      }
   652  `,
   653  		},
   654  		"map_update_element": {
   655  			diff: computed.Diff{
   656  				Renderer: Map(map[string]computed.Diff{
   657  					"element_one": {
   658  						Renderer: Primitive("old", "new", cty.String),
   659  						Action:   plans.Update,
   660  					},
   661  				}),
   662  				Action: plans.Update,
   663  			},
   664  			expected: `
   665  {
   666        ~ "element_one" = "old" -> "new"
   667      }
   668  `,
   669  		},
   670  		"map_delete_element": {
   671  			diff: computed.Diff{
   672  				Renderer: Map(map[string]computed.Diff{
   673  					"element_one": {
   674  						Renderer: Primitive("old", nil, cty.String),
   675  						Action:   plans.Delete,
   676  					},
   677  				}),
   678  				Action: plans.Update,
   679  			},
   680  			expected: `
   681  {
   682        - "element_one" = "old" -> null
   683      }
   684  `,
   685  		},
   686  		"map_update_forces_replacement": {
   687  			diff: computed.Diff{
   688  				Renderer: Map(map[string]computed.Diff{
   689  					"element_one": {
   690  						Renderer: Primitive("old", "new", cty.String),
   691  						Action:   plans.Update,
   692  					},
   693  				}),
   694  				Action:  plans.Update,
   695  				Replace: true,
   696  			},
   697  			expected: `
   698  { # forces replacement
   699        ~ "element_one" = "old" -> "new"
   700      }
   701  `,
   702  		},
   703  		"map_ignore_unchanged_elements": {
   704  			diff: computed.Diff{
   705  				Renderer: Map(map[string]computed.Diff{
   706  					"element_one": {
   707  						Renderer: Primitive(nil, "new", cty.String),
   708  						Action:   plans.Create,
   709  					},
   710  					"element_two": {
   711  						Renderer: Primitive("old", "old", cty.String),
   712  						Action:   plans.NoOp,
   713  					},
   714  					"element_three": {
   715  						Renderer: Primitive("old", "new", cty.String),
   716  						Action:   plans.Update,
   717  					},
   718  				}),
   719  				Action: plans.Update,
   720  			},
   721  			expected: `
   722  {
   723        + "element_one"   = "new"
   724        ~ "element_three" = "old" -> "new"
   725          # (1 unchanged element hidden)
   726      }
   727  `,
   728  		},
   729  		"map_create_sensitive_element": {
   730  			diff: computed.Diff{
   731  				Renderer: Map(map[string]computed.Diff{
   732  					"element_one": {
   733  						Renderer: Sensitive(computed.Diff{
   734  							Renderer: Primitive(nil, 1.0, cty.Number),
   735  							Action:   plans.Create,
   736  						}, false, true),
   737  						Action: plans.Create,
   738  					},
   739  				}),
   740  				Action: plans.Update,
   741  			},
   742  			expected: `
   743  {
   744        + "element_one" = (sensitive value)
   745      }
   746  `,
   747  		},
   748  		"map_update_sensitive_element": {
   749  			diff: computed.Diff{
   750  				Renderer: Map(map[string]computed.Diff{
   751  					"element_one": {
   752  						Renderer: Sensitive(computed.Diff{
   753  							Renderer: Primitive(0.0, 1.0, cty.Number),
   754  							Action:   plans.Update,
   755  						}, true, true),
   756  						Action: plans.Update,
   757  					},
   758  				}),
   759  				Action: plans.Update,
   760  			},
   761  			expected: `
   762  {
   763        ~ "element_one" = (sensitive value)
   764      }
   765  `,
   766  		},
   767  		"map_update_sensitive_element_status": {
   768  			diff: computed.Diff{
   769  				Renderer: Map(map[string]computed.Diff{
   770  					"element_one": {
   771  						Renderer: Sensitive(computed.Diff{
   772  							Renderer: Primitive(0.0, 0.0, cty.Number),
   773  							Action:   plans.NoOp,
   774  						}, true, false),
   775  						Action: plans.Update,
   776  					},
   777  				}),
   778  				Action: plans.Update,
   779  			},
   780  			expected: `
   781  {
   782        # Warning: this attribute value will no longer be marked as sensitive
   783        # after applying this change. The value is unchanged.
   784        ~ "element_one" = (sensitive value)
   785      }
   786  `,
   787  		},
   788  		"map_delete_sensitive_element": {
   789  			diff: computed.Diff{
   790  				Renderer: Map(map[string]computed.Diff{
   791  					"element_one": {
   792  						Renderer: Sensitive(computed.Diff{
   793  							Renderer: Primitive(0.0, nil, cty.Number),
   794  							Action:   plans.Delete,
   795  						}, true, false),
   796  						Action: plans.Delete,
   797  					},
   798  				}),
   799  				Action: plans.Update,
   800  			},
   801  			expected: `
   802  {
   803        - "element_one" = (sensitive value) -> null
   804      }
   805  `,
   806  		},
   807  		"map_create_computed_element": {
   808  			diff: computed.Diff{
   809  				Renderer: Map(map[string]computed.Diff{
   810  					"element_one": {
   811  						Renderer: Unknown(computed.Diff{}),
   812  						Action:   plans.Create,
   813  					},
   814  				}),
   815  				Action: plans.Update,
   816  			},
   817  			expected: `
   818  {
   819        + "element_one" = (known after apply)
   820      }
   821  `,
   822  		},
   823  		"map_update_computed_element": {
   824  			diff: computed.Diff{
   825  				Renderer: Map(map[string]computed.Diff{
   826  					"element_one": {
   827  						Renderer: Unknown(computed.Diff{
   828  							Renderer: Primitive(1.0, nil, cty.Number),
   829  							Action:   plans.Delete,
   830  						}),
   831  						Action: plans.Update,
   832  					},
   833  				}),
   834  				Action: plans.Update,
   835  			},
   836  			expected: `
   837  {
   838        ~ "element_one" = 1 -> (known after apply)
   839      }
   840  `,
   841  		},
   842  		"map_aligns_key": {
   843  			diff: computed.Diff{
   844  				Renderer: Map(map[string]computed.Diff{
   845  					"element_one": {
   846  						Renderer: Primitive(1.0, 2.0, cty.Number),
   847  						Action:   plans.Update,
   848  					},
   849  					"element_two": {
   850  						Renderer: Primitive(1.0, 3.0, cty.Number),
   851  						Action:   plans.Update,
   852  					},
   853  					"element_three": {
   854  						Renderer: Primitive(1.0, 4.0, cty.Number),
   855  						Action:   plans.Update,
   856  					},
   857  				}),
   858  				Action: plans.Update,
   859  			},
   860  			expected: `
   861  {
   862        ~ "element_one"   = 1 -> 2
   863        ~ "element_three" = 1 -> 4
   864        ~ "element_two"   = 1 -> 3
   865      }
   866  `,
   867  		},
   868  		"nested_map_does_not_align_keys": {
   869  			diff: computed.Diff{
   870  				Renderer: NestedMap(map[string]computed.Diff{
   871  					"element_one": {
   872  						Renderer: Primitive(1.0, 2.0, cty.Number),
   873  						Action:   plans.Update,
   874  					},
   875  					"element_two": {
   876  						Renderer: Primitive(1.0, 3.0, cty.Number),
   877  						Action:   plans.Update,
   878  					},
   879  					"element_three": {
   880  						Renderer: Primitive(1.0, 4.0, cty.Number),
   881  						Action:   plans.Update,
   882  					},
   883  				}),
   884  				Action: plans.Update,
   885  			},
   886  			expected: `
   887  {
   888        ~ "element_one" = 1 -> 2
   889        ~ "element_three" = 1 -> 4
   890        ~ "element_two" = 1 -> 3
   891      }
   892  `,
   893  		},
   894  		"list_create_empty": {
   895  			diff: computed.Diff{
   896  				Renderer: List([]computed.Diff{}),
   897  				Action:   plans.Create,
   898  			},
   899  			expected: "[]",
   900  		},
   901  		"list_create": {
   902  			diff: computed.Diff{
   903  				Renderer: List([]computed.Diff{
   904  					{
   905  						Renderer: Primitive(nil, 1.0, cty.Number),
   906  						Action:   plans.Create,
   907  					},
   908  				}),
   909  				Action: plans.Create,
   910  			},
   911  			expected: `
   912  [
   913        + 1,
   914      ]
   915  `,
   916  		},
   917  		"list_delete_empty": {
   918  			diff: computed.Diff{
   919  				Renderer: List([]computed.Diff{}),
   920  				Action:   plans.Delete,
   921  			},
   922  			expected: "[] -> null",
   923  		},
   924  		"list_delete": {
   925  			diff: computed.Diff{
   926  				Renderer: List([]computed.Diff{
   927  					{
   928  						Renderer: Primitive(1.0, nil, cty.Number),
   929  						Action:   plans.Delete,
   930  					},
   931  				}),
   932  				Action: plans.Delete,
   933  			},
   934  			expected: `
   935  [
   936        - 1,
   937      ] -> null
   938  `,
   939  		},
   940  		"list_create_element": {
   941  			diff: computed.Diff{
   942  				Renderer: List([]computed.Diff{
   943  					{
   944  						Renderer: Primitive(nil, 1.0, cty.Number),
   945  						Action:   plans.Create,
   946  					},
   947  				}),
   948  				Action: plans.Update,
   949  			},
   950  			expected: `
   951  [
   952        + 1,
   953      ]
   954  `,
   955  		},
   956  		"list_update_element": {
   957  			diff: computed.Diff{
   958  				Renderer: List([]computed.Diff{
   959  					{
   960  						Renderer: Primitive(0.0, 1.0, cty.Number),
   961  						Action:   plans.Update,
   962  					},
   963  				}),
   964  				Action: plans.Update,
   965  			},
   966  			expected: `
   967  [
   968        ~ 0 -> 1,
   969      ]
   970  `,
   971  		},
   972  		"list_replace_element": {
   973  			diff: computed.Diff{
   974  				Renderer: List([]computed.Diff{
   975  					{
   976  						Renderer: Primitive(0.0, nil, cty.Number),
   977  						Action:   plans.Delete,
   978  					},
   979  					{
   980  						Renderer: Primitive(nil, 1.0, cty.Number),
   981  						Action:   plans.Create,
   982  					},
   983  				}),
   984  				Action: plans.Update,
   985  			},
   986  			expected: `
   987  [
   988        - 0,
   989        + 1,
   990      ]
   991  `,
   992  		},
   993  		"list_delete_element": {
   994  			diff: computed.Diff{
   995  				Renderer: List([]computed.Diff{
   996  					{
   997  						Renderer: Primitive(0.0, nil, cty.Number),
   998  						Action:   plans.Delete,
   999  					},
  1000  				}),
  1001  				Action: plans.Update,
  1002  			},
  1003  			expected: `
  1004  [
  1005        - 0,
  1006      ]
  1007  `,
  1008  		},
  1009  		"list_update_forces_replacement": {
  1010  			diff: computed.Diff{
  1011  				Renderer: List([]computed.Diff{
  1012  					{
  1013  						Renderer: Primitive(0.0, 1.0, cty.Number),
  1014  						Action:   plans.Update,
  1015  					},
  1016  				}),
  1017  				Action:  plans.Update,
  1018  				Replace: true,
  1019  			},
  1020  			expected: `
  1021  [ # forces replacement
  1022        ~ 0 -> 1,
  1023      ]
  1024  `,
  1025  		},
  1026  		"list_update_ignores_unchanged": {
  1027  			diff: computed.Diff{
  1028  				Renderer: NestedList([]computed.Diff{
  1029  					{
  1030  						Renderer: Primitive(0.0, 0.0, cty.Number),
  1031  						Action:   plans.NoOp,
  1032  					},
  1033  					{
  1034  						Renderer: Primitive(1.0, 1.0, cty.Number),
  1035  						Action:   plans.NoOp,
  1036  					},
  1037  					{
  1038  						Renderer: Primitive(2.0, 5.0, cty.Number),
  1039  						Action:   plans.Update,
  1040  					},
  1041  					{
  1042  						Renderer: Primitive(3.0, 3.0, cty.Number),
  1043  						Action:   plans.NoOp,
  1044  					},
  1045  					{
  1046  						Renderer: Primitive(4.0, 4.0, cty.Number),
  1047  						Action:   plans.NoOp,
  1048  					},
  1049  				}),
  1050  				Action: plans.Update,
  1051  			},
  1052  			expected: `
  1053  [
  1054        ~ 2 -> 5,
  1055          # (4 unchanged elements hidden)
  1056      ]
  1057  `,
  1058  		},
  1059  		"list_update_ignored_unchanged_with_context": {
  1060  			diff: computed.Diff{
  1061  				Renderer: List([]computed.Diff{
  1062  					{
  1063  						Renderer: Primitive(0.0, 0.0, cty.Number),
  1064  						Action:   plans.NoOp,
  1065  					},
  1066  					{
  1067  						Renderer: Primitive(1.0, 1.0, cty.Number),
  1068  						Action:   plans.NoOp,
  1069  					},
  1070  					{
  1071  						Renderer: Primitive(2.0, 5.0, cty.Number),
  1072  						Action:   plans.Update,
  1073  					},
  1074  					{
  1075  						Renderer: Primitive(3.0, 3.0, cty.Number),
  1076  						Action:   plans.NoOp,
  1077  					},
  1078  					{
  1079  						Renderer: Primitive(4.0, 4.0, cty.Number),
  1080  						Action:   plans.NoOp,
  1081  					},
  1082  				}),
  1083  				Action: plans.Update,
  1084  			},
  1085  			expected: `
  1086  [
  1087          # (1 unchanged element hidden)
  1088          1,
  1089        ~ 2 -> 5,
  1090          3,
  1091          # (1 unchanged element hidden)
  1092      ]
  1093  `,
  1094  		},
  1095  		"list_create_sensitive_element": {
  1096  			diff: computed.Diff{
  1097  				Renderer: List([]computed.Diff{
  1098  					{
  1099  						Renderer: Sensitive(computed.Diff{
  1100  							Renderer: Primitive(nil, 1.0, cty.Number),
  1101  							Action:   plans.Create,
  1102  						}, false, true),
  1103  						Action: plans.Create,
  1104  					},
  1105  				}),
  1106  				Action: plans.Update,
  1107  			},
  1108  			expected: `
  1109  [
  1110        + (sensitive value),
  1111      ]
  1112  `,
  1113  		},
  1114  		"list_delete_sensitive_element": {
  1115  			diff: computed.Diff{
  1116  				Renderer: List([]computed.Diff{
  1117  					{
  1118  						Renderer: Sensitive(computed.Diff{
  1119  							Renderer: Primitive(1.0, nil, cty.Number),
  1120  							Action:   plans.Delete,
  1121  						}, true, false),
  1122  						Action: plans.Delete,
  1123  					},
  1124  				}),
  1125  				Action: plans.Update,
  1126  			},
  1127  			expected: `
  1128  [
  1129        - (sensitive value),
  1130      ]
  1131  `,
  1132  		},
  1133  		"list_update_sensitive_element": {
  1134  			diff: computed.Diff{
  1135  				Renderer: List([]computed.Diff{
  1136  					{
  1137  						Renderer: Sensitive(computed.Diff{
  1138  							Renderer: Primitive(0.0, 1.0, cty.Number),
  1139  							Action:   plans.Update,
  1140  						}, true, true),
  1141  						Action: plans.Update,
  1142  					},
  1143  				}),
  1144  				Action: plans.Update,
  1145  			},
  1146  			expected: `
  1147  [
  1148        ~ (sensitive value),
  1149      ]
  1150  `,
  1151  		},
  1152  		"list_update_sensitive_element_status": {
  1153  			diff: computed.Diff{
  1154  				Renderer: List([]computed.Diff{
  1155  					{
  1156  						Renderer: Sensitive(computed.Diff{
  1157  							Renderer: Primitive(1.0, 1.0, cty.Number),
  1158  							Action:   plans.NoOp,
  1159  						}, false, true),
  1160  						Action: plans.Update,
  1161  					},
  1162  				}),
  1163  				Action: plans.Update,
  1164  			},
  1165  			expected: `
  1166  [
  1167        # Warning: this attribute value will be marked as sensitive and will not
  1168        # display in UI output after applying this change. The value is unchanged.
  1169        ~ (sensitive value),
  1170      ]
  1171  `,
  1172  		},
  1173  		"list_create_computed_element": {
  1174  			diff: computed.Diff{
  1175  				Renderer: List([]computed.Diff{
  1176  					{
  1177  						Renderer: Unknown(computed.Diff{}),
  1178  						Action:   plans.Create,
  1179  					},
  1180  				}),
  1181  				Action: plans.Update,
  1182  			},
  1183  			expected: `
  1184  [
  1185        + (known after apply),
  1186      ]
  1187  `,
  1188  		},
  1189  		"list_update_computed_element": {
  1190  			diff: computed.Diff{
  1191  				Renderer: List([]computed.Diff{
  1192  					{
  1193  						Renderer: Unknown(computed.Diff{
  1194  							Renderer: Primitive(0.0, nil, cty.Number),
  1195  							Action:   plans.Delete,
  1196  						}),
  1197  						Action: plans.Update,
  1198  					},
  1199  				}),
  1200  				Action: plans.Update,
  1201  			},
  1202  			expected: `
  1203  [
  1204        ~ 0 -> (known after apply),
  1205      ]
  1206  `,
  1207  		},
  1208  		"set_create_empty": {
  1209  			diff: computed.Diff{
  1210  				Renderer: Set([]computed.Diff{}),
  1211  				Action:   plans.Create,
  1212  			},
  1213  			expected: "[]",
  1214  		},
  1215  		"set_create": {
  1216  			diff: computed.Diff{
  1217  				Renderer: Set([]computed.Diff{
  1218  					{
  1219  						Renderer: Primitive(nil, 1.0, cty.Number),
  1220  						Action:   plans.Create,
  1221  					},
  1222  				}),
  1223  				Action: plans.Create,
  1224  			},
  1225  			expected: `
  1226  [
  1227        + 1,
  1228      ]
  1229  `,
  1230  		},
  1231  		"set_delete_empty": {
  1232  			diff: computed.Diff{
  1233  				Renderer: Set([]computed.Diff{}),
  1234  				Action:   plans.Delete,
  1235  			},
  1236  			expected: "[] -> null",
  1237  		},
  1238  		"set_delete": {
  1239  			diff: computed.Diff{
  1240  				Renderer: Set([]computed.Diff{
  1241  					{
  1242  						Renderer: Primitive(1.0, nil, cty.Number),
  1243  						Action:   plans.Delete,
  1244  					},
  1245  				}),
  1246  				Action: plans.Delete,
  1247  			},
  1248  			expected: `
  1249  [
  1250        - 1,
  1251      ] -> null
  1252  `,
  1253  		},
  1254  		"set_create_element": {
  1255  			diff: computed.Diff{
  1256  				Renderer: Set([]computed.Diff{
  1257  					{
  1258  						Renderer: Primitive(nil, 1.0, cty.Number),
  1259  						Action:   plans.Create,
  1260  					},
  1261  				}),
  1262  				Action: plans.Update,
  1263  			},
  1264  			expected: `
  1265  [
  1266        + 1,
  1267      ]
  1268  `,
  1269  		},
  1270  		"set_update_element": {
  1271  			diff: computed.Diff{
  1272  				Renderer: Set([]computed.Diff{
  1273  					{
  1274  						Renderer: Primitive(0.0, 1.0, cty.Number),
  1275  						Action:   plans.Update,
  1276  					},
  1277  				}),
  1278  				Action: plans.Update,
  1279  			},
  1280  			expected: `
  1281  [
  1282        ~ 0 -> 1,
  1283      ]
  1284  `,
  1285  		},
  1286  		"set_replace_element": {
  1287  			diff: computed.Diff{
  1288  				Renderer: Set([]computed.Diff{
  1289  					{
  1290  						Renderer: Primitive(0.0, nil, cty.Number),
  1291  						Action:   plans.Delete,
  1292  					},
  1293  					{
  1294  						Renderer: Primitive(nil, 1.0, cty.Number),
  1295  						Action:   plans.Create,
  1296  					},
  1297  				}),
  1298  				Action: plans.Update,
  1299  			},
  1300  			expected: `
  1301  [
  1302        - 0,
  1303        + 1,
  1304      ]
  1305  `,
  1306  		},
  1307  		"set_delete_element": {
  1308  			diff: computed.Diff{
  1309  				Renderer: Set([]computed.Diff{
  1310  					{
  1311  						Renderer: Primitive(0.0, nil, cty.Number),
  1312  						Action:   plans.Delete,
  1313  					},
  1314  				}),
  1315  				Action: plans.Update,
  1316  			},
  1317  			expected: `
  1318  [
  1319        - 0,
  1320      ]
  1321  `,
  1322  		},
  1323  		"set_update_forces_replacement": {
  1324  			diff: computed.Diff{
  1325  				Renderer: Set([]computed.Diff{
  1326  					{
  1327  						Renderer: Primitive(0.0, 1.0, cty.Number),
  1328  						Action:   plans.Update,
  1329  					},
  1330  				}),
  1331  				Action:  plans.Update,
  1332  				Replace: true,
  1333  			},
  1334  			expected: `
  1335  [ # forces replacement
  1336        ~ 0 -> 1,
  1337      ]
  1338  `,
  1339  		},
  1340  		"nested_set_update_forces_replacement": {
  1341  			diff: computed.Diff{
  1342  				Renderer: NestedSet([]computed.Diff{
  1343  					{
  1344  						Renderer: Object(map[string]computed.Diff{
  1345  							"attribute_one": {
  1346  								Renderer: Primitive(0.0, 1.0, cty.Number),
  1347  								Action:   plans.Update,
  1348  							},
  1349  						}),
  1350  						Action: plans.Update,
  1351  					},
  1352  				}),
  1353  				Action:  plans.Update,
  1354  				Replace: true,
  1355  			},
  1356  			expected: `
  1357  [
  1358        ~ { # forces replacement
  1359            ~ attribute_one = 0 -> 1
  1360          },
  1361      ]
  1362  `,
  1363  		},
  1364  		"set_update_ignores_unchanged": {
  1365  			diff: computed.Diff{
  1366  				Renderer: Set([]computed.Diff{
  1367  					{
  1368  						Renderer: Primitive(0.0, 0.0, cty.Number),
  1369  						Action:   plans.NoOp,
  1370  					},
  1371  					{
  1372  						Renderer: Primitive(1.0, 1.0, cty.Number),
  1373  						Action:   plans.NoOp,
  1374  					},
  1375  					{
  1376  						Renderer: Primitive(2.0, 5.0, cty.Number),
  1377  						Action:   plans.Update,
  1378  					},
  1379  					{
  1380  						Renderer: Primitive(3.0, 3.0, cty.Number),
  1381  						Action:   plans.NoOp,
  1382  					},
  1383  					{
  1384  						Renderer: Primitive(4.0, 4.0, cty.Number),
  1385  						Action:   plans.NoOp,
  1386  					},
  1387  				}),
  1388  				Action: plans.Update,
  1389  			},
  1390  			expected: `
  1391  [
  1392        ~ 2 -> 5,
  1393          # (4 unchanged elements hidden)
  1394      ]
  1395  `,
  1396  		},
  1397  		"set_create_sensitive_element": {
  1398  			diff: computed.Diff{
  1399  				Renderer: Set([]computed.Diff{
  1400  					{
  1401  						Renderer: Sensitive(computed.Diff{
  1402  							Renderer: Primitive(nil, 1.0, cty.Number),
  1403  							Action:   plans.Create,
  1404  						}, false, true),
  1405  						Action: plans.Create,
  1406  					},
  1407  				}),
  1408  				Action: plans.Update,
  1409  			},
  1410  			expected: `
  1411  [
  1412        + (sensitive value),
  1413      ]
  1414  `,
  1415  		},
  1416  		"set_delete_sensitive_element": {
  1417  			diff: computed.Diff{
  1418  				Renderer: Set([]computed.Diff{
  1419  					{
  1420  						Renderer: Sensitive(computed.Diff{
  1421  							Renderer: Primitive(1.0, nil, cty.Number),
  1422  							Action:   plans.Delete,
  1423  						}, false, true),
  1424  						Action: plans.Delete,
  1425  					},
  1426  				}),
  1427  				Action: plans.Update,
  1428  			},
  1429  			expected: `
  1430  [
  1431        - (sensitive value),
  1432      ]
  1433  `,
  1434  		},
  1435  		"set_update_sensitive_element": {
  1436  			diff: computed.Diff{
  1437  				Renderer: Set([]computed.Diff{
  1438  					{
  1439  						Renderer: Sensitive(computed.Diff{
  1440  							Renderer: Primitive(0.0, 1.0, cty.Number),
  1441  							Action:   plans.Update,
  1442  						}, true, true),
  1443  						Action: plans.Update,
  1444  					},
  1445  				}),
  1446  				Action: plans.Update,
  1447  			},
  1448  			expected: `
  1449  [
  1450        ~ (sensitive value),
  1451      ]
  1452  `,
  1453  		},
  1454  		"set_update_sensitive_element_status": {
  1455  			diff: computed.Diff{
  1456  				Renderer: Set([]computed.Diff{
  1457  					{
  1458  						Renderer: Sensitive(computed.Diff{
  1459  							Renderer: Primitive(1.0, 2.0, cty.Number),
  1460  							Action:   plans.Update,
  1461  						}, false, true),
  1462  						Action: plans.Update,
  1463  					},
  1464  				}),
  1465  				Action: plans.Update,
  1466  			},
  1467  			expected: `
  1468  [
  1469        # Warning: this attribute value will be marked as sensitive and will not
  1470        # display in UI output after applying this change.
  1471        ~ (sensitive value),
  1472      ]
  1473  `,
  1474  		},
  1475  		"set_create_computed_element": {
  1476  			diff: computed.Diff{
  1477  				Renderer: Set([]computed.Diff{
  1478  					{
  1479  						Renderer: Unknown(computed.Diff{}),
  1480  						Action:   plans.Create,
  1481  					},
  1482  				}),
  1483  				Action: plans.Update,
  1484  			},
  1485  			expected: `
  1486  [
  1487        + (known after apply),
  1488      ]
  1489  `,
  1490  		},
  1491  		"set_update_computed_element": {
  1492  			diff: computed.Diff{
  1493  				Renderer: Set([]computed.Diff{
  1494  					{
  1495  						Renderer: Unknown(computed.Diff{
  1496  							Renderer: Primitive(0.0, nil, cty.Number),
  1497  							Action:   plans.Delete,
  1498  						}),
  1499  						Action: plans.Update,
  1500  					},
  1501  				}),
  1502  				Action: plans.Update,
  1503  			},
  1504  			expected: `
  1505  [
  1506        ~ 0 -> (known after apply),
  1507      ]
  1508  `,
  1509  		},
  1510  		"create_empty_block": {
  1511  			diff: computed.Diff{
  1512  				Renderer: Block(nil, Blocks{}),
  1513  				Action:   plans.Create,
  1514  			},
  1515  			expected: "{}",
  1516  		},
  1517  		"create_populated_block": {
  1518  			diff: computed.Diff{
  1519  				Renderer: Block(map[string]computed.Diff{
  1520  					"string": {
  1521  						Renderer: Primitive(nil, "root", cty.String),
  1522  						Action:   plans.Create,
  1523  					},
  1524  					"boolean": {
  1525  						Renderer: Primitive(nil, true, cty.Bool),
  1526  						Action:   plans.Create,
  1527  					},
  1528  				}, Blocks{
  1529  					SingleBlocks: map[string]computed.Diff{
  1530  						"nested_block": {
  1531  							Renderer: Block(map[string]computed.Diff{
  1532  								"string": {
  1533  									Renderer: Primitive(nil, "one", cty.String),
  1534  									Action:   plans.Create,
  1535  								},
  1536  							}, Blocks{}),
  1537  							Action: plans.Create,
  1538  						},
  1539  						"nested_block_two": {
  1540  							Renderer: Block(map[string]computed.Diff{
  1541  								"string": {
  1542  									Renderer: Primitive(nil, "two", cty.String),
  1543  									Action:   plans.Create,
  1544  								},
  1545  							}, Blocks{}),
  1546  							Action: plans.Create,
  1547  						},
  1548  					},
  1549  				}),
  1550  				Action: plans.Create,
  1551  			},
  1552  			expected: `
  1553  {
  1554        + boolean = true
  1555        + string  = "root"
  1556  
  1557        + nested_block {
  1558            + string = "one"
  1559          }
  1560  
  1561        + nested_block_two {
  1562            + string = "two"
  1563          }
  1564      }`,
  1565  		},
  1566  		"update_empty_block": {
  1567  			diff: computed.Diff{
  1568  				Renderer: Block(map[string]computed.Diff{
  1569  					"string": {
  1570  						Renderer: Primitive(nil, "root", cty.String),
  1571  						Action:   plans.Create,
  1572  					},
  1573  					"boolean": {
  1574  						Renderer: Primitive(nil, true, cty.Bool),
  1575  						Action:   plans.Create,
  1576  					},
  1577  				}, Blocks{
  1578  					SingleBlocks: map[string]computed.Diff{
  1579  						"nested_block": {
  1580  
  1581  							Renderer: Block(map[string]computed.Diff{
  1582  								"string": {
  1583  									Renderer: Primitive(nil, "one", cty.String),
  1584  									Action:   plans.Create,
  1585  								},
  1586  							}, Blocks{}),
  1587  							Action: plans.Create,
  1588  						},
  1589  						"nested_block_two": {
  1590  
  1591  							Renderer: Block(map[string]computed.Diff{
  1592  								"string": {
  1593  									Renderer: Primitive(nil, "two", cty.String),
  1594  									Action:   plans.Create,
  1595  								},
  1596  							}, Blocks{}),
  1597  							Action: plans.Create,
  1598  						},
  1599  					},
  1600  				}),
  1601  				Action: plans.Update,
  1602  			},
  1603  			expected: `
  1604  {
  1605        + boolean = true
  1606        + string  = "root"
  1607  
  1608        + nested_block {
  1609            + string = "one"
  1610          }
  1611  
  1612        + nested_block_two {
  1613            + string = "two"
  1614          }
  1615      }`,
  1616  		},
  1617  		"update_populated_block": {
  1618  			diff: computed.Diff{
  1619  				Renderer: Block(map[string]computed.Diff{
  1620  					"string": {
  1621  						Renderer: Primitive(nil, "root", cty.String),
  1622  						Action:   plans.Create,
  1623  					},
  1624  					"boolean": {
  1625  						Renderer: Primitive(false, true, cty.Bool),
  1626  						Action:   plans.Update,
  1627  					},
  1628  				}, Blocks{
  1629  					SingleBlocks: map[string]computed.Diff{
  1630  						"nested_block": {
  1631  							Renderer: Block(map[string]computed.Diff{
  1632  								"string": {
  1633  									Renderer: Primitive(nil, "one", cty.String),
  1634  									Action:   plans.NoOp,
  1635  								},
  1636  							}, Blocks{}),
  1637  							Action: plans.NoOp,
  1638  						},
  1639  						"nested_block_two": {
  1640  							Renderer: Block(map[string]computed.Diff{
  1641  								"string": {
  1642  									Renderer: Primitive(nil, "two", cty.String),
  1643  									Action:   plans.Create,
  1644  								},
  1645  							}, Blocks{}),
  1646  							Action: plans.Create,
  1647  						},
  1648  					},
  1649  				}),
  1650  				Action: plans.Update,
  1651  			},
  1652  			expected: `
  1653  {
  1654        ~ boolean = false -> true
  1655        + string  = "root"
  1656  
  1657        + nested_block_two {
  1658            + string = "two"
  1659          }
  1660  
  1661          # (1 unchanged block hidden)
  1662      }`,
  1663  		},
  1664  		"clear_populated_block": {
  1665  			diff: computed.Diff{
  1666  				Renderer: Block(map[string]computed.Diff{
  1667  					"string": {
  1668  						Renderer: Primitive("root", nil, cty.String),
  1669  						Action:   plans.Delete,
  1670  					},
  1671  					"boolean": {
  1672  						Renderer: Primitive(true, nil, cty.Bool),
  1673  						Action:   plans.Delete,
  1674  					},
  1675  				}, Blocks{
  1676  					SingleBlocks: map[string]computed.Diff{
  1677  						"nested_block": {
  1678  							Renderer: Block(map[string]computed.Diff{
  1679  								"string": {
  1680  									Renderer: Primitive("one", nil, cty.String),
  1681  									Action:   plans.Delete,
  1682  								},
  1683  							}, Blocks{}),
  1684  							Action: plans.Delete,
  1685  						},
  1686  						"nested_block_two": {
  1687  							Renderer: Block(map[string]computed.Diff{
  1688  								"string": {
  1689  									Renderer: Primitive("two", nil, cty.String),
  1690  									Action:   plans.Delete,
  1691  								},
  1692  							}, Blocks{}),
  1693  							Action: plans.Delete,
  1694  						},
  1695  					},
  1696  				}),
  1697  				Action: plans.Update,
  1698  			},
  1699  			expected: `
  1700  {
  1701        - boolean = true -> null
  1702        - string  = "root" -> null
  1703  
  1704        - nested_block {
  1705            - string = "one" -> null
  1706          }
  1707  
  1708        - nested_block_two {
  1709            - string = "two" -> null
  1710          }
  1711      }`,
  1712  		},
  1713  		"delete_populated_block": {
  1714  			diff: computed.Diff{
  1715  				Renderer: Block(map[string]computed.Diff{
  1716  					"string": {
  1717  						Renderer: Primitive("root", nil, cty.String),
  1718  						Action:   plans.Delete,
  1719  					},
  1720  					"boolean": {
  1721  						Renderer: Primitive(true, nil, cty.Bool),
  1722  						Action:   plans.Delete,
  1723  					},
  1724  				}, Blocks{
  1725  					SingleBlocks: map[string]computed.Diff{
  1726  						"nested_block": {
  1727  							Renderer: Block(map[string]computed.Diff{
  1728  								"string": {
  1729  									Renderer: Primitive("one", nil, cty.String),
  1730  									Action:   plans.Delete,
  1731  								},
  1732  							}, Blocks{}),
  1733  							Action: plans.Delete,
  1734  						},
  1735  						"nested_block_two": {
  1736  							Renderer: Block(map[string]computed.Diff{
  1737  								"string": {
  1738  									Renderer: Primitive("two", nil, cty.String),
  1739  									Action:   plans.Delete,
  1740  								},
  1741  							}, Blocks{}),
  1742  							Action: plans.Delete,
  1743  						},
  1744  					},
  1745  				}),
  1746  				Action: plans.Delete,
  1747  			},
  1748  			expected: `
  1749  {
  1750        - boolean = true -> null
  1751        - string  = "root" -> null
  1752  
  1753        - nested_block {
  1754            - string = "one" -> null
  1755          }
  1756  
  1757        - nested_block_two {
  1758            - string = "two" -> null
  1759          }
  1760      }`,
  1761  		},
  1762  		"list_block_update": {
  1763  			diff: computed.Diff{
  1764  				Renderer: Block(
  1765  					nil,
  1766  					Blocks{
  1767  						ListBlocks: map[string][]computed.Diff{
  1768  							"list_blocks": {
  1769  								{
  1770  									Renderer: Block(map[string]computed.Diff{
  1771  										"number": {
  1772  											Renderer: Primitive(1.0, 2.0, cty.Number),
  1773  											Action:   plans.Update,
  1774  										},
  1775  										"string": {
  1776  											Renderer: Primitive(nil, "new", cty.String),
  1777  											Action:   plans.Create,
  1778  										},
  1779  									}, Blocks{}),
  1780  									Action: plans.Update,
  1781  								},
  1782  								{
  1783  									Renderer: Block(map[string]computed.Diff{
  1784  										"number": {
  1785  											Renderer: Primitive(1.0, nil, cty.Number),
  1786  											Action:   plans.Delete,
  1787  										},
  1788  										"string": {
  1789  											Renderer: Primitive("old", "new", cty.String),
  1790  											Action:   plans.Update,
  1791  										},
  1792  									}, Blocks{}),
  1793  									Action: plans.Update,
  1794  								},
  1795  							},
  1796  						},
  1797  					}),
  1798  			},
  1799  			expected: `
  1800  {
  1801        ~ list_blocks {
  1802            ~ number = 1 -> 2
  1803            + string = "new"
  1804          }
  1805        ~ list_blocks {
  1806            - number = 1 -> null
  1807            ~ string = "old" -> "new"
  1808          }
  1809      }`,
  1810  		},
  1811  		"set_block_update": {
  1812  			diff: computed.Diff{
  1813  				Renderer: Block(
  1814  					nil,
  1815  					Blocks{
  1816  						SetBlocks: map[string][]computed.Diff{
  1817  							"set_blocks": {
  1818  								{
  1819  									Renderer: Block(map[string]computed.Diff{
  1820  										"number": {
  1821  											Renderer: Primitive(1.0, 2.0, cty.Number),
  1822  											Action:   plans.Update,
  1823  										},
  1824  										"string": {
  1825  											Renderer: Primitive(nil, "new", cty.String),
  1826  											Action:   plans.Create,
  1827  										},
  1828  									}, Blocks{}),
  1829  									Action: plans.Update,
  1830  								},
  1831  								{
  1832  									Renderer: Block(map[string]computed.Diff{
  1833  										"number": {
  1834  											Renderer: Primitive(1.0, nil, cty.Number),
  1835  											Action:   plans.Delete,
  1836  										},
  1837  										"string": {
  1838  											Renderer: Primitive("old", "new", cty.String),
  1839  											Action:   plans.Update,
  1840  										},
  1841  									}, Blocks{}),
  1842  									Action: plans.Update,
  1843  								},
  1844  							},
  1845  						},
  1846  					}),
  1847  			},
  1848  			expected: `
  1849  {
  1850        ~ set_blocks {
  1851            ~ number = 1 -> 2
  1852            + string = "new"
  1853          }
  1854        ~ set_blocks {
  1855            - number = 1 -> null
  1856            ~ string = "old" -> "new"
  1857          }
  1858      }`,
  1859  		},
  1860  		"map_block_update": {
  1861  			diff: computed.Diff{
  1862  				Renderer: Block(
  1863  					nil,
  1864  					Blocks{
  1865  						MapBlocks: map[string]map[string]computed.Diff{
  1866  							"list_blocks": {
  1867  								"key_one": {
  1868  									Renderer: Block(map[string]computed.Diff{
  1869  										"number": {
  1870  											Renderer: Primitive(1.0, 2.0, cty.Number),
  1871  											Action:   plans.Update,
  1872  										},
  1873  										"string": {
  1874  											Renderer: Primitive(nil, "new", cty.String),
  1875  											Action:   plans.Create,
  1876  										},
  1877  									}, Blocks{}),
  1878  									Action: plans.Update,
  1879  								},
  1880  								"key:two": {
  1881  									Renderer: Block(map[string]computed.Diff{
  1882  										"number": {
  1883  											Renderer: Primitive(1.0, nil, cty.Number),
  1884  											Action:   plans.Delete,
  1885  										},
  1886  										"string": {
  1887  											Renderer: Primitive("old", "new", cty.String),
  1888  											Action:   plans.Update,
  1889  										},
  1890  									}, Blocks{}),
  1891  									Action: plans.Update,
  1892  								},
  1893  							},
  1894  						},
  1895  					}),
  1896  			},
  1897  			expected: `
  1898  {
  1899        ~ list_blocks "key:two" {
  1900            - number = 1 -> null
  1901            ~ string = "old" -> "new"
  1902          }
  1903        ~ list_blocks "key_one" {
  1904            ~ number = 1 -> 2
  1905            + string = "new"
  1906          }
  1907      }
  1908  `,
  1909  		},
  1910  		"sensitive_block": {
  1911  			diff: computed.Diff{
  1912  				Renderer: SensitiveBlock(computed.Diff{
  1913  					Renderer: Block(nil, Blocks{}),
  1914  					Action:   plans.NoOp,
  1915  				}, true, true),
  1916  				Action: plans.Update,
  1917  			},
  1918  			expected: `
  1919  {
  1920        # At least one attribute in this block is (or was) sensitive,
  1921        # so its contents will not be displayed.
  1922      }
  1923  `,
  1924  		},
  1925  		"delete_empty_block": {
  1926  			diff: computed.Diff{
  1927  				Renderer: Block(nil, Blocks{}),
  1928  				Action:   plans.Delete,
  1929  			},
  1930  			expected: "{}",
  1931  		},
  1932  		"block_escapes_keys": {
  1933  			diff: computed.Diff{
  1934  				Renderer: Block(map[string]computed.Diff{
  1935  					"attribute_one": {
  1936  						Renderer: Primitive(1.0, 2.0, cty.Number),
  1937  						Action:   plans.Update,
  1938  					},
  1939  					"attribute:two": {
  1940  						Renderer: Primitive(2.0, 3.0, cty.Number),
  1941  						Action:   plans.Update,
  1942  					},
  1943  					"attribute_six": {
  1944  						Renderer: Primitive(3.0, 4.0, cty.Number),
  1945  						Action:   plans.Update,
  1946  					},
  1947  				}, Blocks{
  1948  					SingleBlocks: map[string]computed.Diff{
  1949  						"nested_block:one": {
  1950  							Renderer: Block(map[string]computed.Diff{
  1951  								"string": {
  1952  									Renderer: Primitive("one", "four", cty.String),
  1953  									Action:   plans.Update,
  1954  								},
  1955  							}, Blocks{}),
  1956  							Action: plans.Update,
  1957  						},
  1958  						"nested_block_two": {
  1959  							Renderer: Block(map[string]computed.Diff{
  1960  								"string": {
  1961  									Renderer: Primitive("two", "three", cty.String),
  1962  									Action:   plans.Update,
  1963  								},
  1964  							}, Blocks{}),
  1965  							Action: plans.Update,
  1966  						},
  1967  					},
  1968  				}),
  1969  				Action: plans.Update,
  1970  			},
  1971  			expected: `
  1972  {
  1973        ~ "attribute:two" = 2 -> 3
  1974        ~ attribute_one   = 1 -> 2
  1975        ~ attribute_six   = 3 -> 4
  1976  
  1977        ~ "nested_block:one" {
  1978            ~ string = "one" -> "four"
  1979          }
  1980  
  1981        ~ nested_block_two {
  1982            ~ string = "two" -> "three"
  1983          }
  1984      }`,
  1985  		},
  1986  		"block_always_includes_important_attributes": {
  1987  			diff: computed.Diff{
  1988  				Renderer: Block(map[string]computed.Diff{
  1989  					"id": {
  1990  						Renderer: Primitive("root", "root", cty.String),
  1991  						Action:   plans.NoOp,
  1992  					},
  1993  					"boolean": {
  1994  						Renderer: Primitive(false, false, cty.Bool),
  1995  						Action:   plans.NoOp,
  1996  					},
  1997  				}, Blocks{
  1998  					SingleBlocks: map[string]computed.Diff{
  1999  						"nested_block": {
  2000  							Renderer: Block(map[string]computed.Diff{
  2001  								"string": {
  2002  									Renderer: Primitive("one", "one", cty.String),
  2003  									Action:   plans.NoOp,
  2004  								},
  2005  							}, Blocks{}),
  2006  							Action: plans.NoOp,
  2007  						},
  2008  						"nested_block_two": {
  2009  							Renderer: Block(map[string]computed.Diff{
  2010  								"string": {
  2011  									Renderer: Primitive("two", "two", cty.String),
  2012  									Action:   plans.NoOp,
  2013  								},
  2014  							}, Blocks{}),
  2015  							Action: plans.NoOp,
  2016  						},
  2017  					},
  2018  				}),
  2019  				Action: plans.NoOp,
  2020  			},
  2021  			expected: `
  2022  {
  2023          id      = "root"
  2024          # (1 unchanged attribute hidden)
  2025  
  2026          # (2 unchanged blocks hidden)
  2027      }`,
  2028  		},
  2029  		"output_map_to_list": {
  2030  			diff: computed.Diff{
  2031  				Renderer: TypeChange(computed.Diff{
  2032  					Renderer: Map(map[string]computed.Diff{
  2033  						"element_one": {
  2034  							Renderer: Primitive(0.0, nil, cty.Number),
  2035  							Action:   plans.Delete,
  2036  						},
  2037  						"element_two": {
  2038  							Renderer: Primitive(1.0, nil, cty.Number),
  2039  							Action:   plans.Delete,
  2040  						},
  2041  					}),
  2042  					Action: plans.Delete,
  2043  				}, computed.Diff{
  2044  					Renderer: List([]computed.Diff{
  2045  						{
  2046  							Renderer: Primitive(nil, 0.0, cty.Number),
  2047  							Action:   plans.Create,
  2048  						},
  2049  						{
  2050  							Renderer: Primitive(nil, 1.0, cty.Number),
  2051  							Action:   plans.Create,
  2052  						},
  2053  					}),
  2054  					Action: plans.Create,
  2055  				}),
  2056  			},
  2057  			expected: `
  2058  {
  2059        - "element_one" = 0
  2060        - "element_two" = 1
  2061      } -> [
  2062        + 0,
  2063        + 1,
  2064      ]
  2065  `,
  2066  		},
  2067  		"json_string_no_symbols": {
  2068  			diff: computed.Diff{
  2069  				Renderer: Primitive("{\"key\":\"value\"}", "{\"key\":\"value\"}", cty.String),
  2070  				Action:   plans.NoOp,
  2071  			},
  2072  			opts: computed.RenderHumanOpts{
  2073  				HideDiffActionSymbols: true,
  2074  				ShowUnchangedChildren: true,
  2075  			},
  2076  			expected: `
  2077  jsonencode(
  2078      {
  2079          key = "value"
  2080      }
  2081  )
  2082  `,
  2083  		},
  2084  	}
  2085  	for name, tc := range tcs {
  2086  		t.Run(name, func(t *testing.T) {
  2087  
  2088  			opts := tc.opts.Clone()
  2089  			opts.Colorize = &colorize
  2090  
  2091  			expected := strings.TrimSpace(tc.expected)
  2092  			actual := tc.diff.RenderHuman(0, opts)
  2093  			if diff := cmp.Diff(expected, actual); len(diff) > 0 {
  2094  				t.Fatalf("\nexpected:\n%s\nactual:\n%s\ndiff:\n%s\n", expected, actual, diff)
  2095  			}
  2096  		})
  2097  	}
  2098  }